commit
098e2d2c1a
@ -0,0 +1,38 @@ |
||||
target/ |
||||
!.mvn/wrapper/maven-wrapper.jar |
||||
!**/src/main/**/target/ |
||||
!**/src/test/**/target/ |
||||
|
||||
### IntelliJ IDEA ### |
||||
.idea/modules.xml |
||||
.idea/jarRepositories.xml |
||||
.idea/compiler.xml |
||||
.idea/libraries/ |
||||
*.iws |
||||
*.iml |
||||
*.ipr |
||||
|
||||
### Eclipse ### |
||||
.apt_generated |
||||
.classpath |
||||
.factorypath |
||||
.project |
||||
.settings |
||||
.springBeans |
||||
.sts4-cache |
||||
|
||||
### NetBeans ### |
||||
/nbproject/private/ |
||||
/nbbuild/ |
||||
/dist/ |
||||
/nbdist/ |
||||
/.nb-gradle/ |
||||
build/ |
||||
!**/src/main/**/build/ |
||||
!**/src/test/**/build/ |
||||
|
||||
### VS Code ### |
||||
.vscode/ |
||||
|
||||
### Mac OS ### |
||||
.DS_Store |
Binary file not shown.
@ -0,0 +1,2 @@ |
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.5/apache-maven-3.8.5-bin.zip |
||||
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar |
@ -0,0 +1,316 @@ |
||||
#!/bin/sh |
||||
# ---------------------------------------------------------------------------- |
||||
# 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 |
||||
# |
||||
# https://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. |
||||
# ---------------------------------------------------------------------------- |
||||
|
||||
# ---------------------------------------------------------------------------- |
||||
# Maven Start Up Batch script |
||||
# |
||||
# Required ENV vars: |
||||
# ------------------ |
||||
# JAVA_HOME - location of a JDK home dir |
||||
# |
||||
# Optional ENV vars |
||||
# ----------------- |
||||
# M2_HOME - location of maven2's installed home dir |
||||
# MAVEN_OPTS - parameters passed to the Java VM when running Maven |
||||
# e.g. to debug Maven itself, use |
||||
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 |
||||
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files |
||||
# ---------------------------------------------------------------------------- |
||||
|
||||
if [ -z "$MAVEN_SKIP_RC" ] ; then |
||||
|
||||
if [ -f /usr/local/etc/mavenrc ] ; then |
||||
. /usr/local/etc/mavenrc |
||||
fi |
||||
|
||||
if [ -f /etc/mavenrc ] ; then |
||||
. /etc/mavenrc |
||||
fi |
||||
|
||||
if [ -f "$HOME/.mavenrc" ] ; then |
||||
. "$HOME/.mavenrc" |
||||
fi |
||||
|
||||
fi |
||||
|
||||
# OS specific support. $var _must_ be set to either true or false. |
||||
cygwin=false; |
||||
darwin=false; |
||||
mingw=false |
||||
case "`uname`" in |
||||
CYGWIN*) cygwin=true ;; |
||||
MINGW*) mingw=true;; |
||||
Darwin*) darwin=true |
||||
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home |
||||
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html |
||||
if [ -z "$JAVA_HOME" ]; then |
||||
if [ -x "/usr/libexec/java_home" ]; then |
||||
export JAVA_HOME="`/usr/libexec/java_home`" |
||||
else |
||||
export JAVA_HOME="/Library/Java/Home" |
||||
fi |
||||
fi |
||||
;; |
||||
esac |
||||
|
||||
if [ -z "$JAVA_HOME" ] ; then |
||||
if [ -r /etc/gentoo-release ] ; then |
||||
JAVA_HOME=`java-config --jre-home` |
||||
fi |
||||
fi |
||||
|
||||
if [ -z "$M2_HOME" ] ; then |
||||
## resolve links - $0 may be a link to maven's home |
||||
PRG="$0" |
||||
|
||||
# need this for relative symlinks |
||||
while [ -h "$PRG" ] ; do |
||||
ls=`ls -ld "$PRG"` |
||||
link=`expr "$ls" : '.*-> \(.*\)$'` |
||||
if expr "$link" : '/.*' > /dev/null; then |
||||
PRG="$link" |
||||
else |
||||
PRG="`dirname "$PRG"`/$link" |
||||
fi |
||||
done |
||||
|
||||
saveddir=`pwd` |
||||
|
||||
M2_HOME=`dirname "$PRG"`/.. |
||||
|
||||
# make it fully qualified |
||||
M2_HOME=`cd "$M2_HOME" && pwd` |
||||
|
||||
cd "$saveddir" |
||||
# echo Using m2 at $M2_HOME |
||||
fi |
||||
|
||||
# For Cygwin, ensure paths are in UNIX format before anything is touched |
||||
if $cygwin ; then |
||||
[ -n "$M2_HOME" ] && |
||||
M2_HOME=`cygpath --unix "$M2_HOME"` |
||||
[ -n "$JAVA_HOME" ] && |
||||
JAVA_HOME=`cygpath --unix "$JAVA_HOME"` |
||||
[ -n "$CLASSPATH" ] && |
||||
CLASSPATH=`cygpath --path --unix "$CLASSPATH"` |
||||
fi |
||||
|
||||
# For Mingw, ensure paths are in UNIX format before anything is touched |
||||
if $mingw ; then |
||||
[ -n "$M2_HOME" ] && |
||||
M2_HOME="`(cd "$M2_HOME"; pwd)`" |
||||
[ -n "$JAVA_HOME" ] && |
||||
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" |
||||
fi |
||||
|
||||
if [ -z "$JAVA_HOME" ]; then |
||||
javaExecutable="`which javac`" |
||||
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then |
||||
# readlink(1) is not available as standard on Solaris 10. |
||||
readLink=`which readlink` |
||||
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then |
||||
if $darwin ; then |
||||
javaHome="`dirname \"$javaExecutable\"`" |
||||
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" |
||||
else |
||||
javaExecutable="`readlink -f \"$javaExecutable\"`" |
||||
fi |
||||
javaHome="`dirname \"$javaExecutable\"`" |
||||
javaHome=`expr "$javaHome" : '\(.*\)/bin'` |
||||
JAVA_HOME="$javaHome" |
||||
export JAVA_HOME |
||||
fi |
||||
fi |
||||
fi |
||||
|
||||
if [ -z "$JAVACMD" ] ; then |
||||
if [ -n "$JAVA_HOME" ] ; then |
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then |
||||
# IBM's JDK on AIX uses strange locations for the executables |
||||
JAVACMD="$JAVA_HOME/jre/sh/java" |
||||
else |
||||
JAVACMD="$JAVA_HOME/bin/java" |
||||
fi |
||||
else |
||||
JAVACMD="`\\unset -f command; \\command -v java`" |
||||
fi |
||||
fi |
||||
|
||||
if [ ! -x "$JAVACMD" ] ; then |
||||
echo "Error: JAVA_HOME is not defined correctly." >&2 |
||||
echo " We cannot execute $JAVACMD" >&2 |
||||
exit 1 |
||||
fi |
||||
|
||||
if [ -z "$JAVA_HOME" ] ; then |
||||
echo "Warning: JAVA_HOME environment variable is not set." |
||||
fi |
||||
|
||||
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher |
||||
|
||||
# traverses directory structure from process work directory to filesystem root |
||||
# first directory with .mvn subdirectory is considered project base directory |
||||
find_maven_basedir() { |
||||
|
||||
if [ -z "$1" ] |
||||
then |
||||
echo "Path not specified to find_maven_basedir" |
||||
return 1 |
||||
fi |
||||
|
||||
basedir="$1" |
||||
wdir="$1" |
||||
while [ "$wdir" != '/' ] ; do |
||||
if [ -d "$wdir"/.mvn ] ; then |
||||
basedir=$wdir |
||||
break |
||||
fi |
||||
# workaround for JBEAP-8937 (on Solaris 10/Sparc) |
||||
if [ -d "${wdir}" ]; then |
||||
wdir=`cd "$wdir/.."; pwd` |
||||
fi |
||||
# end of workaround |
||||
done |
||||
echo "${basedir}" |
||||
} |
||||
|
||||
# concatenates all lines of a file |
||||
concat_lines() { |
||||
if [ -f "$1" ]; then |
||||
echo "$(tr -s '\n' ' ' < "$1")" |
||||
fi |
||||
} |
||||
|
||||
BASE_DIR=`find_maven_basedir "$(pwd)"` |
||||
if [ -z "$BASE_DIR" ]; then |
||||
exit 1; |
||||
fi |
||||
|
||||
########################################################################################## |
||||
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central |
||||
# This allows using the maven wrapper in projects that prohibit checking in binary data. |
||||
########################################################################################## |
||||
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then |
||||
if [ "$MVNW_VERBOSE" = true ]; then |
||||
echo "Found .mvn/wrapper/maven-wrapper.jar" |
||||
fi |
||||
else |
||||
if [ "$MVNW_VERBOSE" = true ]; then |
||||
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." |
||||
fi |
||||
if [ -n "$MVNW_REPOURL" ]; then |
||||
jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" |
||||
else |
||||
jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" |
||||
fi |
||||
while IFS="=" read key value; do |
||||
case "$key" in (wrapperUrl) jarUrl="$value"; break ;; |
||||
esac |
||||
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" |
||||
if [ "$MVNW_VERBOSE" = true ]; then |
||||
echo "Downloading from: $jarUrl" |
||||
fi |
||||
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" |
||||
if $cygwin; then |
||||
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` |
||||
fi |
||||
|
||||
if command -v wget > /dev/null; then |
||||
if [ "$MVNW_VERBOSE" = true ]; then |
||||
echo "Found wget ... using wget" |
||||
fi |
||||
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then |
||||
wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" |
||||
else |
||||
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" |
||||
fi |
||||
elif command -v curl > /dev/null; then |
||||
if [ "$MVNW_VERBOSE" = true ]; then |
||||
echo "Found curl ... using curl" |
||||
fi |
||||
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then |
||||
curl -o "$wrapperJarPath" "$jarUrl" -f |
||||
else |
||||
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f |
||||
fi |
||||
|
||||
else |
||||
if [ "$MVNW_VERBOSE" = true ]; then |
||||
echo "Falling back to using Java to download" |
||||
fi |
||||
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" |
||||
# For Cygwin, switch paths to Windows format before running javac |
||||
if $cygwin; then |
||||
javaClass=`cygpath --path --windows "$javaClass"` |
||||
fi |
||||
if [ -e "$javaClass" ]; then |
||||
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then |
||||
if [ "$MVNW_VERBOSE" = true ]; then |
||||
echo " - Compiling MavenWrapperDownloader.java ..." |
||||
fi |
||||
# Compiling the Java class |
||||
("$JAVA_HOME/bin/javac" "$javaClass") |
||||
fi |
||||
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then |
||||
# Running the downloader |
||||
if [ "$MVNW_VERBOSE" = true ]; then |
||||
echo " - Running MavenWrapperDownloader.java ..." |
||||
fi |
||||
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") |
||||
fi |
||||
fi |
||||
fi |
||||
fi |
||||
########################################################################################## |
||||
# End of extension |
||||
########################################################################################## |
||||
|
||||
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} |
||||
if [ "$MVNW_VERBOSE" = true ]; then |
||||
echo $MAVEN_PROJECTBASEDIR |
||||
fi |
||||
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" |
||||
|
||||
# For Cygwin, switch paths to Windows format before running java |
||||
if $cygwin; then |
||||
[ -n "$M2_HOME" ] && |
||||
M2_HOME=`cygpath --path --windows "$M2_HOME"` |
||||
[ -n "$JAVA_HOME" ] && |
||||
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` |
||||
[ -n "$CLASSPATH" ] && |
||||
CLASSPATH=`cygpath --path --windows "$CLASSPATH"` |
||||
[ -n "$MAVEN_PROJECTBASEDIR" ] && |
||||
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` |
||||
fi |
||||
|
||||
# Provide a "standardized" way to retrieve the CLI args that will |
||||
# work with both Windows and non-Windows executions. |
||||
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" |
||||
export MAVEN_CMD_LINE_ARGS |
||||
|
||||
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain |
||||
|
||||
exec "$JAVACMD" \ |
||||
$MAVEN_OPTS \ |
||||
$MAVEN_DEBUG_OPTS \ |
||||
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ |
||||
"-Dmaven.home=${M2_HOME}" \ |
||||
"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ |
||||
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" |
@ -0,0 +1,188 @@ |
||||
@REM ---------------------------------------------------------------------------- |
||||
@REM Licensed to the Apache Software Foundation (ASF) under one |
||||
@REM or more contributor license agreements. See the NOTICE file |
||||
@REM distributed with this work for additional information |
||||
@REM regarding copyright ownership. The ASF licenses this file |
||||
@REM to you under the Apache License, Version 2.0 (the |
||||
@REM "License"); you may not use this file except in compliance |
||||
@REM with the License. You may obtain a copy of the License at |
||||
@REM |
||||
@REM https://www.apache.org/licenses/LICENSE-2.0 |
||||
@REM |
||||
@REM Unless required by applicable law or agreed to in writing, |
||||
@REM software distributed under the License is distributed on an |
||||
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||
@REM KIND, either express or implied. See the License for the |
||||
@REM specific language governing permissions and limitations |
||||
@REM under the License. |
||||
@REM ---------------------------------------------------------------------------- |
||||
|
||||
@REM ---------------------------------------------------------------------------- |
||||
@REM Maven Start Up Batch script |
||||
@REM |
||||
@REM Required ENV vars: |
||||
@REM JAVA_HOME - location of a JDK home dir |
||||
@REM |
||||
@REM Optional ENV vars |
||||
@REM M2_HOME - location of maven2's installed home dir |
||||
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands |
||||
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending |
||||
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven |
||||
@REM e.g. to debug Maven itself, use |
||||
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 |
||||
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files |
||||
@REM ---------------------------------------------------------------------------- |
||||
|
||||
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' |
||||
@echo off |
||||
@REM set title of command window |
||||
title %0 |
||||
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' |
||||
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% |
||||
|
||||
@REM set %HOME% to equivalent of $HOME |
||||
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") |
||||
|
||||
@REM Execute a user defined script before this one |
||||
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre |
||||
@REM check for pre script, once with legacy .bat ending and once with .cmd ending |
||||
if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* |
||||
if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* |
||||
:skipRcPre |
||||
|
||||
@setlocal |
||||
|
||||
set ERROR_CODE=0 |
||||
|
||||
@REM To isolate internal variables from possible post scripts, we use another setlocal |
||||
@setlocal |
||||
|
||||
@REM ==== START VALIDATION ==== |
||||
if not "%JAVA_HOME%" == "" goto OkJHome |
||||
|
||||
echo. |
||||
echo Error: JAVA_HOME not found in your environment. >&2 |
||||
echo Please set the JAVA_HOME variable in your environment to match the >&2 |
||||
echo location of your Java installation. >&2 |
||||
echo. |
||||
goto error |
||||
|
||||
:OkJHome |
||||
if exist "%JAVA_HOME%\bin\java.exe" goto init |
||||
|
||||
echo. |
||||
echo Error: JAVA_HOME is set to an invalid directory. >&2 |
||||
echo JAVA_HOME = "%JAVA_HOME%" >&2 |
||||
echo Please set the JAVA_HOME variable in your environment to match the >&2 |
||||
echo location of your Java installation. >&2 |
||||
echo. |
||||
goto error |
||||
|
||||
@REM ==== END VALIDATION ==== |
||||
|
||||
:init |
||||
|
||||
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". |
||||
@REM Fallback to current working directory if not found. |
||||
|
||||
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% |
||||
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir |
||||
|
||||
set EXEC_DIR=%CD% |
||||
set WDIR=%EXEC_DIR% |
||||
:findBaseDir |
||||
IF EXIST "%WDIR%"\.mvn goto baseDirFound |
||||
cd .. |
||||
IF "%WDIR%"=="%CD%" goto baseDirNotFound |
||||
set WDIR=%CD% |
||||
goto findBaseDir |
||||
|
||||
:baseDirFound |
||||
set MAVEN_PROJECTBASEDIR=%WDIR% |
||||
cd "%EXEC_DIR%" |
||||
goto endDetectBaseDir |
||||
|
||||
:baseDirNotFound |
||||
set MAVEN_PROJECTBASEDIR=%EXEC_DIR% |
||||
cd "%EXEC_DIR%" |
||||
|
||||
:endDetectBaseDir |
||||
|
||||
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig |
||||
|
||||
@setlocal EnableExtensions EnableDelayedExpansion |
||||
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a |
||||
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% |
||||
|
||||
:endReadAdditionalConfig |
||||
|
||||
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" |
||||
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" |
||||
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain |
||||
|
||||
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" |
||||
|
||||
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( |
||||
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B |
||||
) |
||||
|
||||
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central |
||||
@REM This allows using the maven wrapper in projects that prohibit checking in binary data. |
||||
if exist %WRAPPER_JAR% ( |
||||
if "%MVNW_VERBOSE%" == "true" ( |
||||
echo Found %WRAPPER_JAR% |
||||
) |
||||
) else ( |
||||
if not "%MVNW_REPOURL%" == "" ( |
||||
SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" |
||||
) |
||||
if "%MVNW_VERBOSE%" == "true" ( |
||||
echo Couldn't find %WRAPPER_JAR%, downloading it ... |
||||
echo Downloading from: %DOWNLOAD_URL% |
||||
) |
||||
|
||||
powershell -Command "&{"^ |
||||
"$webclient = new-object System.Net.WebClient;"^ |
||||
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ |
||||
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ |
||||
"}"^ |
||||
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ |
||||
"}" |
||||
if "%MVNW_VERBOSE%" == "true" ( |
||||
echo Finished downloading %WRAPPER_JAR% |
||||
) |
||||
) |
||||
@REM End of extension |
||||
|
||||
@REM Provide a "standardized" way to retrieve the CLI args that will |
||||
@REM work with both Windows and non-Windows executions. |
||||
set MAVEN_CMD_LINE_ARGS=%* |
||||
|
||||
%MAVEN_JAVA_EXE% ^ |
||||
%JVM_CONFIG_MAVEN_PROPS% ^ |
||||
%MAVEN_OPTS% ^ |
||||
%MAVEN_DEBUG_OPTS% ^ |
||||
-classpath %WRAPPER_JAR% ^ |
||||
"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ |
||||
%WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* |
||||
if ERRORLEVEL 1 goto error |
||||
goto end |
||||
|
||||
:error |
||||
set ERROR_CODE=1 |
||||
|
||||
:end |
||||
@endlocal & set ERROR_CODE=%ERROR_CODE% |
||||
|
||||
if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost |
||||
@REM check for post script, once with legacy .bat ending and once with .cmd ending |
||||
if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" |
||||
if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" |
||||
:skipRcPost |
||||
|
||||
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' |
||||
if "%MAVEN_BATCH_PAUSE%"=="on" pause |
||||
|
||||
if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% |
||||
|
||||
cmd /C exit /B %ERROR_CODE% |
@ -0,0 +1,2 @@ |
||||
@echo off |
||||
jpackage --type app-image -n EagleEyeRegister -m com.keyware.regtool/com.keyware.regtool.Application --runtime-image target\App --temp target\temp --dest target\EagleEyeRegister --icon src\main\resources\logo.ico |
@ -0,0 +1,69 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" |
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> |
||||
<modelVersion>4.0.0</modelVersion> |
||||
|
||||
<groupId>com.keyware</groupId> |
||||
<artifactId>EagleEyeRegister</artifactId> |
||||
<version>1.0-SNAPSHOT</version> |
||||
<name>EagleEyeRegister</name> |
||||
|
||||
<properties> |
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
||||
<junit.version>5.9.2</junit.version> |
||||
</properties> |
||||
|
||||
<dependencies> |
||||
<dependency> |
||||
<groupId>org.openjfx</groupId> |
||||
<artifactId>javafx-controls</artifactId> |
||||
<version>17.0.6</version> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.openjfx</groupId> |
||||
<artifactId>javafx-fxml</artifactId> |
||||
<version>17.0.6</version> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.openjfx</groupId> |
||||
<artifactId>javafx-web</artifactId> |
||||
<version>17.0.6</version> |
||||
</dependency> |
||||
</dependencies> |
||||
|
||||
<build> |
||||
<plugins> |
||||
<plugin> |
||||
<groupId>org.apache.maven.plugins</groupId> |
||||
<artifactId>maven-compiler-plugin</artifactId> |
||||
<version>3.11.0</version> |
||||
<configuration> |
||||
<source>11</source> |
||||
<target>11</target> |
||||
</configuration> |
||||
</plugin> |
||||
<plugin> |
||||
<groupId>org.openjfx</groupId> |
||||
<artifactId>javafx-maven-plugin</artifactId> |
||||
<version>0.0.8</version> |
||||
<executions> |
||||
<execution> |
||||
<!-- Default configuration for running with: mvn clean javafx:run --> |
||||
<id>default-cli</id> |
||||
<configuration> |
||||
<mainClass>com.keyware.regtool/com.keyware.regtool.Application</mainClass> |
||||
<launcher>App</launcher> |
||||
<jlinkZipName>App</jlinkZipName> |
||||
<jlinkImageName>App</jlinkImageName> |
||||
<noManPages>true</noManPages> |
||||
<stripDebug>true</stripDebug> |
||||
<noHeaderFiles>true</noHeaderFiles> |
||||
</configuration> |
||||
</execution> |
||||
</executions> |
||||
</plugin> |
||||
|
||||
</plugins> |
||||
</build> |
||||
</project> |
@ -0,0 +1,168 @@ |
||||
package cn.hutool.core.annotation; |
||||
|
||||
import cn.hutool.core.annotation.scanner.AnnotationScanner; |
||||
import cn.hutool.core.collection.CollUtil; |
||||
import cn.hutool.core.lang.Assert; |
||||
import cn.hutool.core.map.MapUtil; |
||||
import cn.hutool.core.util.ObjectUtil; |
||||
|
||||
import java.lang.annotation.Annotation; |
||||
import java.util.Collection; |
||||
import java.util.Comparator; |
||||
import java.util.LinkedHashMap; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* {@link AnnotationSynthesizer}的基本实现 |
||||
* |
||||
* @author huangchengxing |
||||
*/ |
||||
public abstract class AbstractAnnotationSynthesizer<T> implements AnnotationSynthesizer { |
||||
|
||||
/** |
||||
* 合成注解来源最初来源 |
||||
*/ |
||||
protected final T source; |
||||
|
||||
/** |
||||
* 包含根注解以及其元注解在内的全部注解实例 |
||||
*/ |
||||
protected final Map<Class<? extends Annotation>, SynthesizedAnnotation> synthesizedAnnotationMap; |
||||
|
||||
/** |
||||
* 已经合成过的注解对象 |
||||
*/ |
||||
private final Map<Class<? extends Annotation>, Annotation> synthesizedProxyAnnotations; |
||||
|
||||
/** |
||||
* 合成注解选择器 |
||||
*/ |
||||
protected final SynthesizedAnnotationSelector annotationSelector; |
||||
|
||||
/** |
||||
* 合成注解属性处理器 |
||||
*/ |
||||
protected final Collection<SynthesizedAnnotationPostProcessor> postProcessors; |
||||
|
||||
/** |
||||
* 注解扫描器 |
||||
*/ |
||||
protected final AnnotationScanner annotationScanner; |
||||
|
||||
/** |
||||
* 构造一个注解合成器 |
||||
* |
||||
* @param source 当前查找的注解对象 |
||||
* @param annotationSelector 合成注解选择器 |
||||
* @param annotationPostProcessors 注解后置处理器 |
||||
* @param annotationScanner 注解扫描器,该扫描器需要支持扫描注解类 |
||||
*/ |
||||
protected AbstractAnnotationSynthesizer( |
||||
T source, |
||||
SynthesizedAnnotationSelector annotationSelector, |
||||
Collection<SynthesizedAnnotationPostProcessor> annotationPostProcessors, |
||||
AnnotationScanner annotationScanner) { |
||||
Assert.notNull(source, "source must not null"); |
||||
Assert.notNull(annotationSelector, "annotationSelector must not null"); |
||||
Assert.notNull(annotationPostProcessors, "annotationPostProcessors must not null"); |
||||
Assert.notNull(annotationPostProcessors, "annotationScanner must not null"); |
||||
|
||||
this.source = source; |
||||
this.annotationSelector = annotationSelector; |
||||
this.annotationScanner = annotationScanner; |
||||
this.postProcessors = CollUtil.unmodifiable( |
||||
CollUtil.sort(annotationPostProcessors, Comparator.comparing(SynthesizedAnnotationPostProcessor::order)) |
||||
); |
||||
this.synthesizedProxyAnnotations = new LinkedHashMap<>(); |
||||
this.synthesizedAnnotationMap = MapUtil.unmodifiable(loadAnnotations()); |
||||
annotationPostProcessors.forEach(processor -> |
||||
synthesizedAnnotationMap.values().forEach(synthesized -> processor.process(synthesized, this)) |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* 加载合成注解的必要属性 |
||||
* |
||||
* @return 合成注解 |
||||
*/ |
||||
protected abstract Map<Class<? extends Annotation>, SynthesizedAnnotation> loadAnnotations(); |
||||
|
||||
/** |
||||
* 根据指定的注解类型和对应注解对象,合成最终所需的合成注解 |
||||
* |
||||
* @param annotationType 注解类型 |
||||
* @param annotation 合成注解对象 |
||||
* @param <A> 注解类型 |
||||
* @return 最终所需的合成注解 |
||||
*/ |
||||
protected abstract <A extends Annotation> A synthesize(Class<A> annotationType, SynthesizedAnnotation annotation); |
||||
|
||||
/** |
||||
* 获取合成注解来源最初来源 |
||||
* |
||||
* @return 合成注解来源最初来源 |
||||
*/ |
||||
@Override |
||||
public T getSource() { |
||||
return source; |
||||
} |
||||
|
||||
/** |
||||
* 合成注解选择器 |
||||
* |
||||
* @return 注解选择器 |
||||
*/ |
||||
@Override |
||||
public SynthesizedAnnotationSelector getAnnotationSelector() { |
||||
return annotationSelector; |
||||
} |
||||
|
||||
/** |
||||
* 获取合成注解后置处理器 |
||||
* |
||||
* @return 合成注解后置处理器 |
||||
*/ |
||||
@Override |
||||
public Collection<SynthesizedAnnotationPostProcessor> getAnnotationPostProcessors() { |
||||
return postProcessors; |
||||
} |
||||
|
||||
/** |
||||
* 获取已合成的注解 |
||||
* |
||||
* @param annotationType 注解类型 |
||||
* @return 已合成的注解 |
||||
*/ |
||||
@Override |
||||
public SynthesizedAnnotation getSynthesizedAnnotation(Class<?> annotationType) { |
||||
return synthesizedAnnotationMap.get(annotationType); |
||||
} |
||||
|
||||
/** |
||||
* 获取全部的合成注解 |
||||
* |
||||
* @return 合成注解 |
||||
*/ |
||||
@Override |
||||
public Map<Class<? extends Annotation>, SynthesizedAnnotation> getAllSynthesizedAnnotation() { |
||||
return synthesizedAnnotationMap; |
||||
} |
||||
|
||||
/** |
||||
* 获取合成注解 |
||||
* |
||||
* @param annotationType 注解类型 |
||||
* @param <A> 注解类型 |
||||
* @return 类型 |
||||
*/ |
||||
@SuppressWarnings("unchecked") |
||||
@Override |
||||
public <A extends Annotation> A synthesize(Class<A> annotationType) { |
||||
return (A)synthesizedProxyAnnotations.computeIfAbsent(annotationType, type -> { |
||||
final SynthesizedAnnotation synthesizedAnnotation = synthesizedAnnotationMap.get(annotationType); |
||||
return ObjectUtil.isNull(synthesizedAnnotation) ? |
||||
null : synthesize(annotationType, synthesizedAnnotation); |
||||
}); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,163 @@ |
||||
package cn.hutool.core.annotation; |
||||
|
||||
import cn.hutool.core.lang.Assert; |
||||
import cn.hutool.core.lang.Opt; |
||||
import cn.hutool.core.util.ArrayUtil; |
||||
import cn.hutool.core.util.ObjectUtil; |
||||
|
||||
import java.lang.annotation.Annotation; |
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* {@link SynthesizedAnnotationPostProcessor}的基本实现, |
||||
* 用于处理注解中带有{@link Link}注解的属性。 |
||||
* |
||||
* @author huangchengxing |
||||
* @see MirrorLinkAnnotationPostProcessor |
||||
* @see AliasLinkAnnotationPostProcessor |
||||
*/ |
||||
public abstract class AbstractLinkAnnotationPostProcessor implements SynthesizedAnnotationPostProcessor { |
||||
|
||||
/** |
||||
* 若一个注解属性上存在{@link Link}注解,注解的{@link Link#type()}返回值在{@link #processTypes()}中存在, |
||||
* 且此{@link Link}指定的注解对象在当前的{@link SynthesizedAggregateAnnotation}中存在, |
||||
* 则从聚合器中获取类型对应的合成注解对象,与该对象中的指定属性,然后将全部关联数据交给 |
||||
* {@link #processLinkedAttribute}处理。 |
||||
* |
||||
* @param synthesizedAnnotation 合成的注解 |
||||
* @param synthesizer 合成注解聚合器 |
||||
*/ |
||||
@Override |
||||
public void process(SynthesizedAnnotation synthesizedAnnotation, AnnotationSynthesizer synthesizer) { |
||||
final Map<String, AnnotationAttribute> attributeMap = new HashMap<>(synthesizedAnnotation.getAttributes()); |
||||
attributeMap.forEach((originalAttributeName, originalAttribute) -> { |
||||
// 获取注解
|
||||
final Link link = getLinkAnnotation(originalAttribute, processTypes()); |
||||
if (ObjectUtil.isNull(link)) { |
||||
return; |
||||
} |
||||
// 获取注解属性
|
||||
final SynthesizedAnnotation linkedAnnotation = getLinkedAnnotation(link, synthesizer, synthesizedAnnotation.annotationType()); |
||||
if (ObjectUtil.isNull(linkedAnnotation)) { |
||||
return; |
||||
} |
||||
final AnnotationAttribute linkedAttribute = linkedAnnotation.getAttributes().get(link.attribute()); |
||||
// 处理
|
||||
processLinkedAttribute( |
||||
synthesizer, link, |
||||
synthesizedAnnotation, synthesizedAnnotation.getAttributes().get(originalAttributeName), |
||||
linkedAnnotation, linkedAttribute |
||||
); |
||||
}); |
||||
} |
||||
|
||||
// =========================== 抽象方法 ===========================
|
||||
|
||||
/** |
||||
* 当属性上存在{@link Link}注解时,仅当{@link Link#type()}在本方法返回值内存在时才进行处理 |
||||
* |
||||
* @return 支持处理的{@link RelationType}类型 |
||||
*/ |
||||
protected abstract RelationType[] processTypes(); |
||||
|
||||
/** |
||||
* 对关联的合成注解对象及其关联属性的处理 |
||||
* |
||||
* @param synthesizer 注解合成器 |
||||
* @param annotation {@code originalAttribute}上的{@link Link}注解对象 |
||||
* @param originalAnnotation 当前正在处理的{@link SynthesizedAnnotation}对象 |
||||
* @param originalAttribute {@code originalAnnotation}上的待处理的属性 |
||||
* @param linkedAnnotation {@link Link}指向的关联注解对象 |
||||
* @param linkedAttribute {@link Link}指向的{@code originalAnnotation}中的关联属性,该参数可能为空 |
||||
*/ |
||||
protected abstract void processLinkedAttribute( |
||||
AnnotationSynthesizer synthesizer, Link annotation, |
||||
SynthesizedAnnotation originalAnnotation, AnnotationAttribute originalAttribute, |
||||
SynthesizedAnnotation linkedAnnotation, AnnotationAttribute linkedAttribute |
||||
); |
||||
|
||||
// =========================== @Link注解的处理 ===========================
|
||||
|
||||
/** |
||||
* 从注解属性上获取指定类型的{@link Link}注解 |
||||
* |
||||
* @param attribute 注解属性 |
||||
* @param relationTypes 类型 |
||||
* @return 注解 |
||||
*/ |
||||
protected Link getLinkAnnotation(AnnotationAttribute attribute, RelationType... relationTypes) { |
||||
return Opt.ofNullable(attribute) |
||||
.map(t -> AnnotationUtil.getSynthesizedAnnotation(attribute.getAttribute(), Link.class)) |
||||
.filter(a -> ArrayUtil.contains(relationTypes, a.type())) |
||||
.get(); |
||||
} |
||||
|
||||
/** |
||||
* 从合成注解中获取{@link Link#type()}指定的注解对象 |
||||
* |
||||
* @param annotation {@link Link}注解 |
||||
* @param synthesizer 注解合成器 |
||||
* @param defaultType 默认类型 |
||||
* @return {@link SynthesizedAnnotation} |
||||
*/ |
||||
protected SynthesizedAnnotation getLinkedAnnotation(Link annotation, AnnotationSynthesizer synthesizer, Class<? extends Annotation> defaultType) { |
||||
final Class<?> targetAnnotationType = getLinkedAnnotationType(annotation, defaultType); |
||||
return synthesizer.getSynthesizedAnnotation(targetAnnotationType); |
||||
} |
||||
|
||||
/** |
||||
* 若{@link Link#annotation()}获取的类型{@code Annotation#getClass()},则返回{@code defaultType}, |
||||
* 否则返回{@link Link#annotation()}指定的类型 |
||||
* |
||||
* @param annotation {@link Link}注解 |
||||
* @param defaultType 默认注解类型 |
||||
* @return 注解类型 |
||||
*/ |
||||
protected Class<?> getLinkedAnnotationType(Link annotation, Class<?> defaultType) { |
||||
return ObjectUtil.equals(annotation.annotation(), Annotation.class) ? |
||||
defaultType : annotation.annotation(); |
||||
} |
||||
|
||||
// =========================== 注解属性的校验 ===========================
|
||||
|
||||
/** |
||||
* 校验两个注解属性的返回值类型是否一致 |
||||
* |
||||
* @param original 原属性 |
||||
* @param alias 别名属性 |
||||
*/ |
||||
protected void checkAttributeType(AnnotationAttribute original, AnnotationAttribute alias) { |
||||
Assert.equals( |
||||
original.getAttributeType(), alias.getAttributeType(), |
||||
"return type of the linked attribute [{}] is inconsistent with the original [{}]", |
||||
original.getAttribute(), alias.getAttribute() |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* 检查{@link Link}指向的注解属性是否就是本身 |
||||
* |
||||
* @param original {@link Link}注解的属性 |
||||
* @param linked {@link Link}指向的注解属性 |
||||
*/ |
||||
protected void checkLinkedSelf(AnnotationAttribute original, AnnotationAttribute linked) { |
||||
boolean linkSelf = (original == linked) || ObjectUtil.equals(original.getAttribute(), linked.getAttribute()); |
||||
Assert.isFalse(linkSelf, "cannot link self [{}]", original.getAttribute()); |
||||
} |
||||
|
||||
/** |
||||
* 检查{@link Link}指向的注解属性是否存在 |
||||
* |
||||
* @param original {@link Link}注解的属性 |
||||
* @param linkedAttribute {@link Link}指向的注解属性 |
||||
* @param annotation {@link Link}注解 |
||||
*/ |
||||
protected void checkLinkedAttributeNotNull(AnnotationAttribute original, AnnotationAttribute linkedAttribute, Link annotation) { |
||||
Assert.notNull(linkedAttribute, "cannot find linked attribute [{}] of original [{}] in [{}]", |
||||
original.getAttribute(), annotation.attribute(), |
||||
getLinkedAnnotationType(annotation, original.getAnnotationType()) |
||||
); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,71 @@ |
||||
package cn.hutool.core.annotation; |
||||
|
||||
import cn.hutool.core.lang.Assert; |
||||
import cn.hutool.core.util.ObjectUtil; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.Collection; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* {@link WrappedAnnotationAttribute}的基本实现 |
||||
* |
||||
* @author huangchengxing |
||||
* @see ForceAliasedAnnotationAttribute |
||||
* @see AliasedAnnotationAttribute |
||||
* @see MirroredAnnotationAttribute |
||||
*/ |
||||
public abstract class AbstractWrappedAnnotationAttribute implements WrappedAnnotationAttribute { |
||||
|
||||
protected final AnnotationAttribute original; |
||||
protected final AnnotationAttribute linked; |
||||
|
||||
protected AbstractWrappedAnnotationAttribute(AnnotationAttribute original, AnnotationAttribute linked) { |
||||
Assert.notNull(original, "target must not null"); |
||||
Assert.notNull(linked, "linked must not null"); |
||||
this.original = original; |
||||
this.linked = linked; |
||||
} |
||||
|
||||
@Override |
||||
public AnnotationAttribute getOriginal() { |
||||
return original; |
||||
} |
||||
|
||||
@Override |
||||
public AnnotationAttribute getLinked() { |
||||
return linked; |
||||
} |
||||
|
||||
@Override |
||||
public AnnotationAttribute getNonWrappedOriginal() { |
||||
AnnotationAttribute curr = null; |
||||
AnnotationAttribute next = original; |
||||
while (next != null) { |
||||
curr = next; |
||||
next = next.isWrapped() ? ((WrappedAnnotationAttribute)curr).getOriginal() : null; |
||||
} |
||||
return curr; |
||||
} |
||||
|
||||
@Override |
||||
public Collection<AnnotationAttribute> getAllLinkedNonWrappedAttributes() { |
||||
List<AnnotationAttribute> leafAttributes = new ArrayList<>(); |
||||
collectLeafAttribute(this, leafAttributes); |
||||
return leafAttributes; |
||||
} |
||||
|
||||
private void collectLeafAttribute(AnnotationAttribute curr, List<AnnotationAttribute> leafAttributes) { |
||||
if (ObjectUtil.isNull(curr)) { |
||||
return; |
||||
} |
||||
if (!curr.isWrapped()) { |
||||
leafAttributes.add(curr); |
||||
return; |
||||
} |
||||
WrappedAnnotationAttribute wrappedAttribute = (WrappedAnnotationAttribute)curr; |
||||
collectLeafAttribute(wrappedAttribute.getOriginal(), leafAttributes); |
||||
collectLeafAttribute(wrappedAttribute.getLinked(), leafAttributes); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,27 @@ |
||||
package cn.hutool.core.annotation; |
||||
|
||||
import java.lang.annotation.Annotation; |
||||
|
||||
/** |
||||
* 表示一组被聚合在一起的注解对象 |
||||
* |
||||
* @author huangchengxing |
||||
*/ |
||||
public interface AggregateAnnotation extends Annotation { |
||||
|
||||
/** |
||||
* 在聚合中是否存在的指定类型注解对象 |
||||
* |
||||
* @param annotationType 注解类型 |
||||
* @return 是否 |
||||
*/ |
||||
boolean isAnnotationPresent(Class<? extends Annotation> annotationType); |
||||
|
||||
/** |
||||
* 获取聚合中的全部注解对象 |
||||
* |
||||
* @return 注解对象 |
||||
*/ |
||||
Annotation[] getAnnotations(); |
||||
|
||||
} |
@ -0,0 +1,26 @@ |
||||
package cn.hutool.core.annotation; |
||||
|
||||
import java.lang.annotation.Documented; |
||||
import java.lang.annotation.ElementType; |
||||
import java.lang.annotation.Retention; |
||||
import java.lang.annotation.RetentionPolicy; |
||||
import java.lang.annotation.Target; |
||||
|
||||
/** |
||||
* 别名注解,使用此注解的字段、方法、参数等会有一个别名,用于Bean拷贝、Bean转Map等 |
||||
* |
||||
* @author Looly |
||||
* @since 5.1.1 |
||||
*/ |
||||
@Documented |
||||
@Retention(RetentionPolicy.RUNTIME) |
||||
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER}) |
||||
public @interface Alias { |
||||
|
||||
/** |
||||
* 别名值,即使用此注解要替换成的别名名称 |
||||
* |
||||
* @return 别名值 |
||||
*/ |
||||
String value(); |
||||
} |
@ -0,0 +1,66 @@ |
||||
package cn.hutool.core.annotation; |
||||
|
||||
import cn.hutool.core.lang.Assert; |
||||
import cn.hutool.core.lang.Opt; |
||||
import cn.hutool.core.map.ForestMap; |
||||
import cn.hutool.core.map.LinkedForestMap; |
||||
import cn.hutool.core.map.TreeEntry; |
||||
import cn.hutool.core.util.ClassUtil; |
||||
import cn.hutool.core.util.ObjectUtil; |
||||
|
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* <p>用于处理注解对象中带有{@link Alias}注解的属性。<br> |
||||
* 当该处理器执行完毕后,{@link Alias}注解指向的目标注解的属性将会被包装并替换为 |
||||
* {@link ForceAliasedAnnotationAttribute}。 |
||||
* |
||||
* @author huangchengxing |
||||
* @see Alias |
||||
* @see ForceAliasedAnnotationAttribute |
||||
*/ |
||||
public class AliasAnnotationPostProcessor implements SynthesizedAnnotationPostProcessor { |
||||
|
||||
@Override |
||||
public int order() { |
||||
return Integer.MIN_VALUE; |
||||
} |
||||
|
||||
@Override |
||||
public void process(SynthesizedAnnotation synthesizedAnnotation, AnnotationSynthesizer synthesizer) { |
||||
final Map<String, AnnotationAttribute> attributeMap = synthesizedAnnotation.getAttributes(); |
||||
|
||||
// 记录别名与属性的关系
|
||||
final ForestMap<String, AnnotationAttribute> attributeAliasMappings = new LinkedForestMap<>(false); |
||||
attributeMap.forEach((attributeName, attribute) -> { |
||||
final String alias = Opt.ofNullable(attribute.getAnnotation(Alias.class)) |
||||
.map(Alias::value) |
||||
.orElse(null); |
||||
if (ObjectUtil.isNull(alias)) { |
||||
return; |
||||
} |
||||
final AnnotationAttribute aliasAttribute = attributeMap.get(alias); |
||||
Assert.notNull(aliasAttribute, "no method for alias: [{}]", alias); |
||||
attributeAliasMappings.putLinkedNodes(alias, aliasAttribute, attributeName, attribute); |
||||
}); |
||||
|
||||
// 处理别名
|
||||
attributeMap.forEach((attributeName, attribute) -> { |
||||
final AnnotationAttribute resolvedAttribute = Opt.ofNullable(attributeName) |
||||
.map(attributeAliasMappings::getRootNode) |
||||
.map(TreeEntry::getValue) |
||||
.orElse(attribute); |
||||
Assert.isTrue( |
||||
ObjectUtil.isNull(resolvedAttribute) |
||||
|| ClassUtil.isAssignable(attribute.getAttributeType(), resolvedAttribute.getAttributeType()), |
||||
"return type of the root alias method [{}] is inconsistent with the original [{}]", |
||||
resolvedAttribute.getClass(), attribute.getAttributeType() |
||||
); |
||||
if (attribute != resolvedAttribute) { |
||||
attributeMap.put(attributeName, new ForceAliasedAnnotationAttribute(attribute, resolvedAttribute)); |
||||
} |
||||
}); |
||||
synthesizedAnnotation.setAttributes(attributeMap); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,39 @@ |
||||
package cn.hutool.core.annotation; |
||||
|
||||
import java.lang.annotation.*; |
||||
|
||||
/** |
||||
* <p>{@link Link}的子注解。表示“原始属性”将作为“关联属性”的别名。 |
||||
* <ul> |
||||
* <li>当“原始属性”为默认值时,获取“关联属性”将返回“关联属性”本身的值;</li> |
||||
* <li>当“原始属性”不为默认值时,获取“关联属性”将返回“原始属性”的值;</li> |
||||
* </ul> |
||||
* <b>注意,该注解与{@link Link}、{@link ForceAliasFor}或{@link MirrorFor}一起使用时,将只有被声明在最上面的注解会生效</b> |
||||
* |
||||
* @author huangchengxing |
||||
* @see Link |
||||
* @see RelationType#ALIAS_FOR |
||||
*/ |
||||
@Link(type = RelationType.ALIAS_FOR) |
||||
@Documented |
||||
@Retention(RetentionPolicy.RUNTIME) |
||||
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) |
||||
public @interface AliasFor { |
||||
|
||||
/** |
||||
* 产生关联的注解类型,当不指定时,默认指注释的属性所在的类 |
||||
* |
||||
* @return 注解类型 |
||||
*/ |
||||
@Link(annotation = Link.class, attribute = "annotation", type = RelationType.FORCE_ALIAS_FOR) |
||||
Class<? extends Annotation> annotation() default Annotation.class; |
||||
|
||||
/** |
||||
* {@link #annotation()}指定注解中关联的属性 |
||||
* |
||||
* @return 关联属性 |
||||
*/ |
||||
@Link(annotation = Link.class, attribute = "attribute", type = RelationType.FORCE_ALIAS_FOR) |
||||
String attribute() default ""; |
||||
|
||||
} |
@ -0,0 +1,126 @@ |
||||
package cn.hutool.core.annotation; |
||||
|
||||
import cn.hutool.core.lang.Assert; |
||||
import cn.hutool.core.lang.Opt; |
||||
import cn.hutool.core.util.ObjectUtil; |
||||
|
||||
import java.util.function.BinaryOperator; |
||||
|
||||
/** |
||||
* <p>用于处理注解对象中带有{@link Link}注解,且{@link Link#type()}为 |
||||
* {@link RelationType#ALIAS_FOR}或{@link RelationType#FORCE_ALIAS_FOR}的属性。<br> |
||||
* 当该处理器执行完毕后,{@link Link}注解指向的目标注解的属性将会被包装并替换为 |
||||
* {@link AliasedAnnotationAttribute}或{@link ForceAliasedAnnotationAttribute}。 |
||||
* |
||||
* @author huangchengxing |
||||
* @see RelationType#ALIAS_FOR |
||||
* @see AliasedAnnotationAttribute |
||||
* @see RelationType#FORCE_ALIAS_FOR |
||||
* @see ForceAliasedAnnotationAttribute |
||||
*/ |
||||
public class AliasLinkAnnotationPostProcessor extends AbstractLinkAnnotationPostProcessor { |
||||
|
||||
private static final RelationType[] PROCESSED_RELATION_TYPES = new RelationType[]{ RelationType.ALIAS_FOR, RelationType.FORCE_ALIAS_FOR }; |
||||
|
||||
@Override |
||||
public int order() { |
||||
return Integer.MIN_VALUE + 2; |
||||
} |
||||
|
||||
/** |
||||
* 该处理器只处理{@link Link#type()}类型为{@link RelationType#ALIAS_FOR}和{@link RelationType#FORCE_ALIAS_FOR}的注解属性 |
||||
* |
||||
* @return 含有{@link RelationType#ALIAS_FOR}和{@link RelationType#FORCE_ALIAS_FOR}的数组 |
||||
*/ |
||||
@Override |
||||
protected RelationType[] processTypes() { |
||||
return PROCESSED_RELATION_TYPES; |
||||
} |
||||
|
||||
/** |
||||
* 获取{@link Link}指向的目标注解属性,并根据{@link Link#type()}的类型是 |
||||
* {@link RelationType#ALIAS_FOR}或{@link RelationType#FORCE_ALIAS_FOR} |
||||
* 将目标注解属性包装为{@link AliasedAnnotationAttribute}或{@link ForceAliasedAnnotationAttribute}, |
||||
* 然后用包装后注解属性在对应的合成注解中替换原始的目标注解属性 |
||||
* |
||||
* @param synthesizer 注解合成器 |
||||
* @param annotation {@code originalAttribute}上的{@link Link}注解对象 |
||||
* @param originalAnnotation 当前正在处理的{@link SynthesizedAnnotation}对象 |
||||
* @param originalAttribute {@code originalAnnotation}上的待处理的属性 |
||||
* @param linkedAnnotation {@link Link}指向的关联注解对象 |
||||
* @param linkedAttribute {@link Link}指向的{@code originalAnnotation}中的关联属性,该参数可能为空 |
||||
*/ |
||||
@Override |
||||
protected void processLinkedAttribute( |
||||
AnnotationSynthesizer synthesizer, Link annotation, |
||||
SynthesizedAnnotation originalAnnotation, AnnotationAttribute originalAttribute, |
||||
SynthesizedAnnotation linkedAnnotation, AnnotationAttribute linkedAttribute) { |
||||
// 校验别名关系
|
||||
checkAliasRelation(annotation, originalAttribute, linkedAttribute); |
||||
// 处理aliasFor类型的关系
|
||||
if (RelationType.ALIAS_FOR.equals(annotation.type())) { |
||||
wrappingLinkedAttribute(synthesizer, originalAttribute, linkedAttribute, AliasedAnnotationAttribute::new); |
||||
return; |
||||
} |
||||
// 处理forceAliasFor类型的关系
|
||||
wrappingLinkedAttribute(synthesizer, originalAttribute, linkedAttribute, ForceAliasedAnnotationAttribute::new); |
||||
} |
||||
|
||||
/** |
||||
* 对指定注解属性进行包装,若该属性已被包装过,则递归以其为根节点的树结构,对树上全部的叶子节点进行包装 |
||||
*/ |
||||
private void wrappingLinkedAttribute( |
||||
AnnotationSynthesizer synthesizer, AnnotationAttribute originalAttribute, AnnotationAttribute aliasAttribute, BinaryOperator<AnnotationAttribute> wrapping) { |
||||
// 不是包装属性
|
||||
if (!aliasAttribute.isWrapped()) { |
||||
processAttribute(synthesizer, originalAttribute, aliasAttribute, wrapping); |
||||
return; |
||||
} |
||||
// 是包装属性
|
||||
final AbstractWrappedAnnotationAttribute wrapper = (AbstractWrappedAnnotationAttribute)aliasAttribute; |
||||
wrapper.getAllLinkedNonWrappedAttributes().forEach( |
||||
t -> processAttribute(synthesizer, originalAttribute, t, wrapping) |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* 获取指定注解属性,然后将其再进行一层包装 |
||||
*/ |
||||
private void processAttribute( |
||||
AnnotationSynthesizer synthesizer, AnnotationAttribute originalAttribute, |
||||
AnnotationAttribute target, BinaryOperator<AnnotationAttribute> wrapping) { |
||||
Opt.ofNullable(target.getAnnotationType()) |
||||
.map(synthesizer::getSynthesizedAnnotation) |
||||
.ifPresent(t -> t.replaceAttribute(target.getAttributeName(), old -> wrapping.apply(old, originalAttribute))); |
||||
} |
||||
|
||||
/** |
||||
* 基本校验 |
||||
*/ |
||||
private void checkAliasRelation(Link annotation, AnnotationAttribute originalAttribute, AnnotationAttribute linkedAttribute) { |
||||
checkLinkedAttributeNotNull(originalAttribute, linkedAttribute, annotation); |
||||
checkAttributeType(originalAttribute, linkedAttribute); |
||||
checkCircularDependency(originalAttribute, linkedAttribute); |
||||
} |
||||
|
||||
/** |
||||
* 检查两个属性是否互为别名 |
||||
*/ |
||||
private void checkCircularDependency(AnnotationAttribute original, AnnotationAttribute alias) { |
||||
checkLinkedSelf(original, alias); |
||||
Link annotation = getLinkAnnotation(alias, RelationType.ALIAS_FOR, RelationType.FORCE_ALIAS_FOR); |
||||
if (ObjectUtil.isNull(annotation)) { |
||||
return; |
||||
} |
||||
final Class<?> aliasAnnotationType = getLinkedAnnotationType(annotation, alias.getAnnotationType()); |
||||
if (ObjectUtil.notEqual(aliasAnnotationType, original.getAnnotationType())) { |
||||
return; |
||||
} |
||||
Assert.notEquals( |
||||
annotation.attribute(), original.getAttributeName(), |
||||
"circular reference between the alias attribute [{}] and the original attribute [{}]", |
||||
alias.getAttribute(), original.getAttribute() |
||||
); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,36 @@ |
||||
package cn.hutool.core.annotation; |
||||
|
||||
/** |
||||
* <p>表示一个具有别名的属性。 |
||||
* 当别名属性值为默认值时,优先返回原属性的值,当别名属性不为默认值时,优先返回别名属性的值 |
||||
* |
||||
* @author huangchengxing |
||||
* @see AliasLinkAnnotationPostProcessor |
||||
* @see RelationType#ALIAS_FOR |
||||
*/ |
||||
public class AliasedAnnotationAttribute extends AbstractWrappedAnnotationAttribute { |
||||
|
||||
protected AliasedAnnotationAttribute(AnnotationAttribute origin, AnnotationAttribute linked) { |
||||
super(origin, linked); |
||||
} |
||||
|
||||
/** |
||||
* 若{@link #linked}为默认值,则返回{@link #original}的值,否则返回{@link #linked}的值 |
||||
* |
||||
* @return 属性值 |
||||
*/ |
||||
@Override |
||||
public Object getValue() { |
||||
return linked.isValueEquivalentToDefaultValue() ? super.getValue() : linked.getValue(); |
||||
} |
||||
|
||||
/** |
||||
* 当{@link #original}与{@link #linked}都为默认值时返回{@code true} |
||||
* |
||||
* @return 是否 |
||||
*/ |
||||
@Override |
||||
public boolean isValueEquivalentToDefaultValue() { |
||||
return linked.isValueEquivalentToDefaultValue() && original.isValueEquivalentToDefaultValue(); |
||||
} |
||||
} |
@ -0,0 +1,104 @@ |
||||
package cn.hutool.core.annotation; |
||||
|
||||
import cn.hutool.core.util.ReflectUtil; |
||||
|
||||
import java.lang.annotation.Annotation; |
||||
import java.lang.reflect.Method; |
||||
|
||||
/** |
||||
* <p>表示注解的某个属性,等同于绑定的调用对象的{@link Method}方法。<br> |
||||
* 在{@link SynthesizedAggregateAnnotation}的解析以及取值过程中, |
||||
* 可以通过设置{@link SynthesizedAnnotation}的注解属性, |
||||
* 从而使得可以从一个注解对象中属性获取另一个注解对象的属性值 |
||||
* |
||||
* <p>一般情况下,注解属性的处理会发生在{@link SynthesizedAnnotationPostProcessor}调用时 |
||||
* |
||||
* @author huangchengxing |
||||
* @see SynthesizedAnnotationPostProcessor |
||||
* @see WrappedAnnotationAttribute |
||||
* @see CacheableAnnotationAttribute |
||||
* @see AbstractWrappedAnnotationAttribute |
||||
* @see ForceAliasedAnnotationAttribute |
||||
* @see AliasedAnnotationAttribute |
||||
* @see MirroredAnnotationAttribute |
||||
*/ |
||||
public interface AnnotationAttribute { |
||||
|
||||
/** |
||||
* 获取注解对象 |
||||
* |
||||
* @return 注解对象 |
||||
*/ |
||||
Annotation getAnnotation(); |
||||
|
||||
/** |
||||
* 获取注解属性对应的方法 |
||||
* |
||||
* @return 注解属性对应的方法 |
||||
*/ |
||||
Method getAttribute(); |
||||
|
||||
/** |
||||
* 获取声明属性的注解类 |
||||
* |
||||
* @return 声明注解的注解类 |
||||
*/ |
||||
default Class<?> getAnnotationType() { |
||||
return getAttribute().getDeclaringClass(); |
||||
} |
||||
|
||||
/** |
||||
* 获取属性名称 |
||||
* |
||||
* @return 属性名称 |
||||
*/ |
||||
default String getAttributeName() { |
||||
return getAttribute().getName(); |
||||
} |
||||
|
||||
/** |
||||
* 获取注解属性 |
||||
* |
||||
* @return 注解属性 |
||||
*/ |
||||
default Object getValue() { |
||||
return ReflectUtil.invoke(getAnnotation(), getAttribute()); |
||||
} |
||||
|
||||
/** |
||||
* 该注解属性的值是否等于默认值 |
||||
* |
||||
* @return 该注解属性的值是否等于默认值 |
||||
*/ |
||||
boolean isValueEquivalentToDefaultValue(); |
||||
|
||||
/** |
||||
* 获取属性类型 |
||||
* |
||||
* @return 属性类型 |
||||
*/ |
||||
default Class<?> getAttributeType() { |
||||
return getAttribute().getReturnType(); |
||||
} |
||||
|
||||
/** |
||||
* 获取属性上的注解 |
||||
* |
||||
* @param <T> 注解类型 |
||||
* @param annotationType 注解类型 |
||||
* @return 注解对象 |
||||
*/ |
||||
default <T extends Annotation> T getAnnotation(Class<T> annotationType) { |
||||
return getAttribute().getAnnotation(annotationType); |
||||
} |
||||
|
||||
/** |
||||
* 当前注解属性是否已经被{@link WrappedAnnotationAttribute}包装 |
||||
* |
||||
* @return boolean |
||||
*/ |
||||
default boolean isWrapped() { |
||||
return false; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,18 @@ |
||||
package cn.hutool.core.annotation; |
||||
|
||||
/** |
||||
* 表示一个可以从当前接口的实现类中,获得特定的属性值 |
||||
*/ |
||||
@FunctionalInterface |
||||
public interface AnnotationAttributeValueProvider { |
||||
|
||||
/** |
||||
* 获取注解属性值 |
||||
* |
||||
* @param attributeName 属性名称 |
||||
* @param attributeType 属性类型 |
||||
* @return 注解属性值 |
||||
*/ |
||||
Object getAttributeValue(String attributeName, Class<?> attributeType); |
||||
|
||||
} |
@ -0,0 +1,88 @@ |
||||
package cn.hutool.core.annotation; |
||||
|
||||
import cn.hutool.core.util.ReflectUtil; |
||||
import cn.hutool.core.util.StrUtil; |
||||
|
||||
import java.io.Serializable; |
||||
import java.lang.annotation.Annotation; |
||||
import java.lang.reflect.InvocationHandler; |
||||
import java.lang.reflect.Method; |
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* 注解代理<br> |
||||
* 通过代理指定注解,可以自定义调用注解的方法逻辑,如支持{@link Alias} 注解 |
||||
* |
||||
* @param <T> 注解类型 |
||||
* @since 5.7.23 |
||||
*/ |
||||
public class AnnotationProxy<T extends Annotation> implements Annotation, InvocationHandler, Serializable { |
||||
private static final long serialVersionUID = 1L; |
||||
|
||||
private final T annotation; |
||||
private final Class<T> type; |
||||
private final Map<String, Object> attributes; |
||||
|
||||
/** |
||||
* 构造 |
||||
* |
||||
* @param annotation 注解 |
||||
*/ |
||||
public AnnotationProxy(T annotation) { |
||||
this.annotation = annotation; |
||||
//noinspection unchecked
|
||||
this.type = (Class<T>) annotation.annotationType(); |
||||
this.attributes = initAttributes(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public Class<? extends Annotation> annotationType() { |
||||
return type; |
||||
} |
||||
|
||||
@Override |
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { |
||||
|
||||
// 注解别名
|
||||
Alias alias = method.getAnnotation(Alias.class); |
||||
if(null != alias){ |
||||
final String name = alias.value(); |
||||
if(StrUtil.isNotBlank(name)){ |
||||
if(!attributes.containsKey(name)){ |
||||
throw new IllegalArgumentException(StrUtil.format("No method for alias: [{}]", name)); |
||||
} |
||||
return attributes.get(name); |
||||
} |
||||
} |
||||
|
||||
final Object value = attributes.get(method.getName()); |
||||
if (value != null) { |
||||
return value; |
||||
} |
||||
return method.invoke(this, args); |
||||
} |
||||
|
||||
/** |
||||
* 初始化注解的属性<br> |
||||
* 此方法预先调用所有注解的方法,将注解方法值缓存于attributes中 |
||||
* |
||||
* @return 属性(方法结果)映射 |
||||
*/ |
||||
private Map<String, Object> initAttributes() { |
||||
final Method[] methods = ReflectUtil.getMethods(this.type); |
||||
final Map<String, Object> attributes = new HashMap<>(methods.length, 1); |
||||
|
||||
for (Method method : methods) { |
||||
// 跳过匿名内部类自动生成的方法
|
||||
if (method.isSynthetic()) { |
||||
continue; |
||||
} |
||||
|
||||
attributes.put(method.getName(), ReflectUtil.invoke(this.annotation, method)); |
||||
} |
||||
|
||||
return attributes; |
||||
} |
||||
} |
@ -0,0 +1,77 @@ |
||||
package cn.hutool.core.annotation; |
||||
|
||||
import java.lang.annotation.Annotation; |
||||
import java.util.Collection; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* <p>注解合成器,用于处理一组给定的与{@link #getSource()}具有直接或间接联系的注解对象, |
||||
* 并返回与原始注解对象具有不同属性的“合成”注解。 |
||||
* |
||||
* <p>合成注解一般被用于处理类层级结果中具有直接或间接关联的注解对象, |
||||
* 当实例被创建时,会获取到这些注解对象,并使用{@link SynthesizedAnnotationSelector}对类型相同的注解进行过滤, |
||||
* 并最终得到类型不重复的有效注解对象。这些有效注解将被包装为{@link SynthesizedAnnotation}, |
||||
* 然后最终用于“合成”一个{@link SynthesizedAggregateAnnotation}。<br> |
||||
* {@link SynthesizedAnnotationSelector}是合成注解生命周期中的第一个钩子, |
||||
* 自定义选择器以拦截原始注解被扫描的过程。 |
||||
* |
||||
* <p>当合成注解完成对待合成注解的扫描,并完成了必要属性的加载后, |
||||
* 将会按顺序依次调用{@link SynthesizedAnnotationPostProcessor}, |
||||
* 注解后置处理器允许用于对完成注解的待合成注解进行二次调整, |
||||
* 该钩子一般用于根据{@link Link}注解对属性进行调整。<br> |
||||
* {@link SynthesizedAnnotationPostProcessor}是合成注解生命周期中的第二个钩子, |
||||
* 自定义后置处理器以拦截原始在转为待合成注解后的初始化过程。 |
||||
* |
||||
* <p>使用{@link #synthesize(Class)}用于获取“合成”后的注解, |
||||
* 该注解对象的属性可能会与原始的对象属性不同。 |
||||
* |
||||
* @author huangchengxing |
||||
*/ |
||||
public interface AnnotationSynthesizer { |
||||
|
||||
/** |
||||
* 获取合成注解来源最初来源 |
||||
* |
||||
* @return 合成注解来源最初来源 |
||||
*/ |
||||
Object getSource(); |
||||
|
||||
/** |
||||
* 合成注解选择器 |
||||
* |
||||
* @return 注解选择器 |
||||
*/ |
||||
SynthesizedAnnotationSelector getAnnotationSelector(); |
||||
|
||||
/** |
||||
* 获取合成注解后置处理器 |
||||
* |
||||
* @return 合成注解后置处理器 |
||||
*/ |
||||
Collection<SynthesizedAnnotationPostProcessor> getAnnotationPostProcessors(); |
||||
|
||||
/** |
||||
* 获取已合成的注解 |
||||
* |
||||
* @param annotationType 注解类型 |
||||
* @return 已合成的注解 |
||||
*/ |
||||
SynthesizedAnnotation getSynthesizedAnnotation(Class<?> annotationType); |
||||
|
||||
/** |
||||
* 获取全部的合成注解 |
||||
* |
||||
* @return 合成注解 |
||||
*/ |
||||
Map<Class<? extends Annotation>, SynthesizedAnnotation> getAllSynthesizedAnnotation(); |
||||
|
||||
/** |
||||
* 获取合成注解 |
||||
* |
||||
* @param annotationType 注解类型 |
||||
* @param <T> 注解类型 |
||||
* @return 类型 |
||||
*/ |
||||
<T extends Annotation> T synthesize(Class<T> annotationType); |
||||
|
||||
} |
@ -0,0 +1,576 @@ |
||||
package cn.hutool.core.annotation; |
||||
|
||||
import cn.hutool.core.annotation.scanner.AnnotationScanner; |
||||
import cn.hutool.core.annotation.scanner.MetaAnnotationScanner; |
||||
import cn.hutool.core.annotation.scanner.MethodAnnotationScanner; |
||||
import cn.hutool.core.annotation.scanner.TypeAnnotationScanner; |
||||
import cn.hutool.core.collection.CollUtil; |
||||
import cn.hutool.core.exceptions.UtilException; |
||||
import cn.hutool.core.lang.Opt; |
||||
import cn.hutool.core.lang.func.Func1; |
||||
import cn.hutool.core.lang.func.LambdaUtil; |
||||
import cn.hutool.core.util.*; |
||||
|
||||
import java.lang.annotation.*; |
||||
import java.lang.invoke.SerializedLambda; |
||||
import java.lang.reflect.AnnotatedElement; |
||||
import java.lang.reflect.Method; |
||||
import java.lang.reflect.Proxy; |
||||
import java.util.*; |
||||
import java.util.function.Predicate; |
||||
import java.util.stream.Collectors; |
||||
|
||||
/** |
||||
* 注解工具类<br> |
||||
* 快速获取注解对象、注解值等工具封装 |
||||
* |
||||
* @author looly |
||||
* @since 4.0.9 |
||||
*/ |
||||
public class AnnotationUtil { |
||||
|
||||
/** |
||||
* 元注解 |
||||
*/ |
||||
static final Set<Class<? extends Annotation>> META_ANNOTATIONS = CollUtil.newHashSet(Target.class, //
|
||||
Retention.class, //
|
||||
Inherited.class, //
|
||||
Documented.class, //
|
||||
SuppressWarnings.class, //
|
||||
Override.class, //
|
||||
Deprecated.class//
|
||||
); |
||||
|
||||
/** |
||||
* 是否为Jdk自带的元注解。<br> |
||||
* 包括: |
||||
* <ul> |
||||
* <li>{@link Target}</li> |
||||
* <li>{@link Retention}</li> |
||||
* <li>{@link Inherited}</li> |
||||
* <li>{@link Documented}</li> |
||||
* <li>{@link SuppressWarnings}</li> |
||||
* <li>{@link Override}</li> |
||||
* <li>{@link Deprecated}</li> |
||||
* </ul> |
||||
* |
||||
* @param annotationType 注解类型 |
||||
* @return 是否为Jdk自带的元注解 |
||||
*/ |
||||
public static boolean isJdkMetaAnnotation(Class<? extends Annotation> annotationType) { |
||||
return META_ANNOTATIONS.contains(annotationType); |
||||
} |
||||
|
||||
/** |
||||
* 是否不为Jdk自带的元注解。<br> |
||||
* 包括: |
||||
* <ul> |
||||
* <li>{@link Target}</li> |
||||
* <li>{@link Retention}</li> |
||||
* <li>{@link Inherited}</li> |
||||
* <li>{@link Documented}</li> |
||||
* <li>{@link SuppressWarnings}</li> |
||||
* <li>{@link Override}</li> |
||||
* <li>{@link Deprecated}</li> |
||||
* </ul> |
||||
* |
||||
* @param annotationType 注解类型 |
||||
* @return 是否为Jdk自带的元注解 |
||||
*/ |
||||
public static boolean isNotJdkMateAnnotation(Class<? extends Annotation> annotationType) { |
||||
return !isJdkMetaAnnotation(annotationType); |
||||
} |
||||
|
||||
/** |
||||
* 将指定的被注解的元素转换为组合注解元素 |
||||
* |
||||
* @param annotationEle 注解元素 |
||||
* @return 组合注解元素 |
||||
*/ |
||||
public static CombinationAnnotationElement toCombination(AnnotatedElement annotationEle) { |
||||
if (annotationEle instanceof CombinationAnnotationElement) { |
||||
return (CombinationAnnotationElement) annotationEle; |
||||
} |
||||
return new CombinationAnnotationElement(annotationEle); |
||||
} |
||||
|
||||
/** |
||||
* 获取指定注解 |
||||
* |
||||
* @param annotationEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission |
||||
* @param isToCombination 是否为转换为组合注解,组合注解可以递归获取注解的注解 |
||||
* @return 注解对象 |
||||
*/ |
||||
public static Annotation[] getAnnotations(AnnotatedElement annotationEle, boolean isToCombination) { |
||||
return getAnnotations(annotationEle, isToCombination, (Predicate<Annotation>) null); |
||||
} |
||||
|
||||
/** |
||||
* 获取组合注解 |
||||
* |
||||
* @param <T> 注解类型 |
||||
* @param annotationEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission |
||||
* @param annotationType 限定的 |
||||
* @return 注解对象数组 |
||||
* @since 5.8.0 |
||||
*/ |
||||
public static <T> T[] getCombinationAnnotations(AnnotatedElement annotationEle, Class<T> annotationType) { |
||||
return getAnnotations(annotationEle, true, annotationType); |
||||
} |
||||
|
||||
/** |
||||
* 获取指定注解 |
||||
* |
||||
* @param <T> 注解类型 |
||||
* @param annotationEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission |
||||
* @param isToCombination 是否为转换为组合注解,组合注解可以递归获取注解的注解 |
||||
* @param annotationType 限定的 |
||||
* @return 注解对象数组 |
||||
* @since 5.8.0 |
||||
*/ |
||||
public static <T> T[] getAnnotations(AnnotatedElement annotationEle, boolean isToCombination, Class<T> annotationType) { |
||||
final Annotation[] annotations = getAnnotations(annotationEle, isToCombination, |
||||
(annotation -> null == annotationType || annotationType.isAssignableFrom(annotation.getClass()))); |
||||
|
||||
final T[] result = ArrayUtil.newArray(annotationType, annotations.length); |
||||
for (int i = 0; i < annotations.length; i++) { |
||||
//noinspection unchecked
|
||||
result[i] = (T) annotations[i]; |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* 获取指定注解 |
||||
* |
||||
* @param annotationEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission |
||||
* @param isToCombination 是否为转换为组合注解,组合注解可以递归获取注解的注解 |
||||
* @param predicate 过滤器,{@link Predicate#test(Object)}返回{@code true}保留,否则不保留 |
||||
* @return 注解对象,如果提供的{@link AnnotatedElement}为{@code null},返回{@code null} |
||||
* @since 5.8.0 |
||||
*/ |
||||
public static Annotation[] getAnnotations(AnnotatedElement annotationEle, boolean isToCombination, Predicate<Annotation> predicate) { |
||||
if (null == annotationEle) { |
||||
return null; |
||||
} |
||||
|
||||
if (isToCombination) { |
||||
if (null == predicate) { |
||||
return toCombination(annotationEle).getAnnotations(); |
||||
} |
||||
return CombinationAnnotationElement.of(annotationEle, predicate).getAnnotations(); |
||||
} |
||||
|
||||
final Annotation[] result = annotationEle.getAnnotations(); |
||||
if (null == predicate) { |
||||
return result; |
||||
} |
||||
return ArrayUtil.filter(result, predicate::test); |
||||
} |
||||
|
||||
/** |
||||
* 获取指定注解 |
||||
* |
||||
* @param <A> 注解类型 |
||||
* @param annotationEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission |
||||
* @param annotationType 注解类型 |
||||
* @return 注解对象 |
||||
*/ |
||||
public static <A extends Annotation> A getAnnotation(AnnotatedElement annotationEle, Class<A> annotationType) { |
||||
return (null == annotationEle) ? null : toCombination(annotationEle).getAnnotation(annotationType); |
||||
} |
||||
|
||||
/** |
||||
* 检查是否包含指定注解指定注解 |
||||
* |
||||
* @param annotationEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission |
||||
* @param annotationType 注解类型 |
||||
* @return 是否包含指定注解 |
||||
* @since 5.4.2 |
||||
*/ |
||||
public static boolean hasAnnotation(AnnotatedElement annotationEle, Class<? extends Annotation> annotationType) { |
||||
return null != getAnnotation(annotationEle, annotationType); |
||||
} |
||||
|
||||
/** |
||||
* 获取指定注解默认值<br> |
||||
* 如果无指定的属性方法返回null |
||||
* |
||||
* @param <T> 注解值类型 |
||||
* @param annotationEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission |
||||
* @param annotationType 注解类型 |
||||
* @return 注解对象 |
||||
* @throws UtilException 调用注解中的方法时执行异常 |
||||
*/ |
||||
public static <T> T getAnnotationValue(AnnotatedElement annotationEle, Class<? extends Annotation> annotationType) throws UtilException { |
||||
return getAnnotationValue(annotationEle, annotationType, "value"); |
||||
} |
||||
|
||||
/** |
||||
* 获取指定注解属性的值<br> |
||||
* 如果无指定的属性方法返回null |
||||
* |
||||
* @param <T> 注解值类型 |
||||
* @param annotationEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission |
||||
* @param annotationType 注解类型 |
||||
* @param propertyName 属性名,例如注解中定义了name()方法,则 此处传入name |
||||
* @return 注解对象 |
||||
* @throws UtilException 调用注解中的方法时执行异常 |
||||
*/ |
||||
public static <T> T getAnnotationValue(AnnotatedElement annotationEle, Class<? extends Annotation> annotationType, String propertyName) throws UtilException { |
||||
final Annotation annotation = getAnnotation(annotationEle, annotationType); |
||||
if (null == annotation) { |
||||
return null; |
||||
} |
||||
|
||||
final Method method = ReflectUtil.getMethodOfObj(annotation, propertyName); |
||||
if (null == method) { |
||||
return null; |
||||
} |
||||
return ReflectUtil.invoke(annotation, method); |
||||
} |
||||
|
||||
/** |
||||
* 获取指定注解属性的值<br> |
||||
* 如果无指定的属性方法返回null |
||||
* |
||||
* @param <A> 注解类型 |
||||
* @param <R> 注解类型值 |
||||
* @param annotationEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission |
||||
* @param propertyName 属性名,例如注解中定义了name()方法,则 此处传入name |
||||
* @return 注解对象 |
||||
* @throws UtilException 调用注解中的方法时执行异常 |
||||
* @since 5.8.9 |
||||
*/ |
||||
public static <A extends Annotation, R> R getAnnotationValue(AnnotatedElement annotationEle, Func1<A, R> propertyName) { |
||||
if (propertyName == null) { |
||||
return null; |
||||
} else { |
||||
final SerializedLambda lambda = LambdaUtil.resolve(propertyName); |
||||
final String instantiatedMethodType = lambda.getInstantiatedMethodType(); |
||||
final Class<A> annotationClass = ClassUtil.loadClass(StrUtil.sub(instantiatedMethodType, 2, StrUtil.indexOf(instantiatedMethodType, ';'))); |
||||
return getAnnotationValue(annotationEle, annotationClass, lambda.getImplMethodName()); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 获取指定注解中所有属性值<br> |
||||
* 如果无指定的属性方法返回null |
||||
* |
||||
* @param annotationEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission |
||||
* @param annotationType 注解类型 |
||||
* @return 注解对象 |
||||
* @throws UtilException 调用注解中的方法时执行异常 |
||||
*/ |
||||
public static Map<String, Object> getAnnotationValueMap(AnnotatedElement annotationEle, Class<? extends Annotation> annotationType) throws UtilException { |
||||
final Annotation annotation = getAnnotation(annotationEle, annotationType); |
||||
if (null == annotation) { |
||||
return null; |
||||
} |
||||
|
||||
final Method[] methods = ReflectUtil.getMethods(annotationType, t -> { |
||||
if (ArrayUtil.isEmpty(t.getParameterTypes())) { |
||||
// 只读取无参方法
|
||||
final String name = t.getName(); |
||||
// 跳过自有的几个方法
|
||||
return (!"hashCode".equals(name)) //
|
||||
&& (!"toString".equals(name)) //
|
||||
&& (!"annotationType".equals(name)); |
||||
} |
||||
return false; |
||||
}); |
||||
|
||||
final HashMap<String, Object> result = new HashMap<>(methods.length, 1); |
||||
for (Method method : methods) { |
||||
result.put(method.getName(), ReflectUtil.invoke(annotation, method)); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* 获取注解类的保留时间,可选值 SOURCE(源码时),CLASS(编译时),RUNTIME(运行时),默认为 CLASS |
||||
* |
||||
* @param annotationType 注解类 |
||||
* @return 保留时间枚举 |
||||
*/ |
||||
public static RetentionPolicy getRetentionPolicy(Class<? extends Annotation> annotationType) { |
||||
final Retention retention = annotationType.getAnnotation(Retention.class); |
||||
if (null == retention) { |
||||
return RetentionPolicy.CLASS; |
||||
} |
||||
return retention.value(); |
||||
} |
||||
|
||||
/** |
||||
* 获取注解类可以用来修饰哪些程序元素,如 TYPE, METHOD, CONSTRUCTOR, FIELD, PARAMETER 等 |
||||
* |
||||
* @param annotationType 注解类 |
||||
* @return 注解修饰的程序元素数组 |
||||
*/ |
||||
public static ElementType[] getTargetType(Class<? extends Annotation> annotationType) { |
||||
final Target target = annotationType.getAnnotation(Target.class); |
||||
if (null == target) { |
||||
return new ElementType[]{ElementType.TYPE, //
|
||||
ElementType.FIELD, //
|
||||
ElementType.METHOD, //
|
||||
ElementType.PARAMETER, //
|
||||
ElementType.CONSTRUCTOR, //
|
||||
ElementType.LOCAL_VARIABLE, //
|
||||
ElementType.ANNOTATION_TYPE, //
|
||||
ElementType.PACKAGE//
|
||||
}; |
||||
} |
||||
return target.value(); |
||||
} |
||||
|
||||
/** |
||||
* 是否会保存到 Javadoc 文档中 |
||||
* |
||||
* @param annotationType 注解类 |
||||
* @return 是否会保存到 Javadoc 文档中 |
||||
*/ |
||||
public static boolean isDocumented(Class<? extends Annotation> annotationType) { |
||||
return annotationType.isAnnotationPresent(Documented.class); |
||||
} |
||||
|
||||
/** |
||||
* 是否可以被继承,默认为 false |
||||
* |
||||
* @param annotationType 注解类 |
||||
* @return 是否会保存到 Javadoc 文档中 |
||||
*/ |
||||
public static boolean isInherited(Class<? extends Annotation> annotationType) { |
||||
return annotationType.isAnnotationPresent(Inherited.class); |
||||
} |
||||
|
||||
/** |
||||
* 扫描注解类,以及注解类的{@link Class}层级结构中的注解,将返回除了{@link #META_ANNOTATIONS}中指定的JDK默认注解外, |
||||
* 按元注解对象与{@code annotationType}的距离和{@link Class#getAnnotations()}顺序排序的注解对象集合 |
||||
* |
||||
* <p>比如:<br> |
||||
* 若{@code annotationType}为 A,且A存在元注解B,B又存在元注解C和D,则有: |
||||
* <pre> |
||||
* |-> C.class [@a, @b] |
||||
* A.class -> B.class [@a] -| |
||||
* |-> D.class [@a, @c] |
||||
* </pre> |
||||
* 扫描A,则该方法最终将返回 {@code [@a, @a, @b, @a, @c]} |
||||
* |
||||
* @param annotationType 注解类 |
||||
* @return 注解对象集合 |
||||
* @see MetaAnnotationScanner |
||||
*/ |
||||
public static List<Annotation> scanMetaAnnotation(Class<? extends Annotation> annotationType) { |
||||
return AnnotationScanner.DIRECTLY_AND_META_ANNOTATION.getAnnotationsIfSupport(annotationType); |
||||
} |
||||
|
||||
/** |
||||
* <p>扫描类以及类的{@link Class}层级结构中的注解,将返回除了{@link #META_ANNOTATIONS}中指定的JDK默认元注解外, |
||||
* 全部类/接口的{@link Class#getAnnotations()}方法返回的注解对象。<br> |
||||
* 层级结构将按广度优先递归,遵循规则如下: |
||||
* <ul> |
||||
* <li>同一层级中,优先处理父类,然后再处理父接口;</li> |
||||
* <li>同一个接口在不同层级出现,优先选择层级距离{@code targetClass}更近的接口;</li> |
||||
* <li>同一个接口在相同层级出现,优先选择其子类/子接口被先解析的那个;</li> |
||||
* </ul> |
||||
* 注解根据其声明类/接口被扫描的顺序排序,若注解都在同一个{@link Class}中被声明,则还会遵循{@link Class#getAnnotations()}的顺序。 |
||||
* |
||||
* <p>比如:<br> |
||||
* 若{@code targetClass}为{@code A.class},且{@code A.class}存在父类{@code B.class}、父接口{@code C.class}, |
||||
* 三个类的注解声明情况如下: |
||||
* <pre> |
||||
* |-> B.class [@a, @b] |
||||
* A.class [@a] -| |
||||
* |-> C.class [@a, @c] |
||||
* </pre> |
||||
* 则该方法最终将返回 {@code [@a, @a, @b, @a, @c]} |
||||
* |
||||
* @param targetClass 类 |
||||
* @return 注解对象集合 |
||||
* @see TypeAnnotationScanner |
||||
*/ |
||||
public static List<Annotation> scanClass(Class<?> targetClass) { |
||||
return AnnotationScanner.TYPE_HIERARCHY.getAnnotationsIfSupport(targetClass); |
||||
} |
||||
|
||||
/** |
||||
* <p>扫描方法,以及该方法所在类的{@link Class}层级结构中的具有相同方法签名的方法, |
||||
* 将返回除了{@link #META_ANNOTATIONS}中指定的JDK默认元注解外, |
||||
* 全部匹配方法上{@link Method#getAnnotations()}方法返回的注解对象。<br> |
||||
* 方法所在类的层级结构将按广度优先递归,遵循规则如下: |
||||
* <ul> |
||||
* <li>同一层级中,优先处理父类,然后再处理父接口;</li> |
||||
* <li>同一个接口在不同层级出现,优先选择层级距离{@code targetClass}更近的接口;</li> |
||||
* <li>同一个接口在相同层级出现,优先选择其子类/子接口被先解析的那个;</li> |
||||
* </ul> |
||||
* 方法上的注解根据方法的声明类/接口被扫描的顺序排序,若注解都在同一个类的同一个方法中被声明,则还会遵循{@link Method#getAnnotations()}的顺序。 |
||||
* |
||||
* <p>比如:<br> |
||||
* 若方法X声明于{@code A.class},且重载/重写自父类{@code B.class},并且父类中的方法X由重写至其实现的接口{@code C.class}, |
||||
* 三个类的注解声明情况如下: |
||||
* <pre> |
||||
* A#X()[@a] -> B#X()[@b] -> C#X()[@c] |
||||
* </pre> |
||||
* 则该方法最终将返回 {@code [@a, @b, @c]} |
||||
* |
||||
* @param method 方法 |
||||
* @return 注解对象集合 |
||||
* @see MethodAnnotationScanner |
||||
*/ |
||||
public static List<Annotation> scanMethod(Method method) { |
||||
return AnnotationScanner.TYPE_HIERARCHY.getAnnotationsIfSupport(method); |
||||
} |
||||
|
||||
/** |
||||
* 设置新的注解的属性(字段)值 |
||||
* |
||||
* @param annotation 注解对象 |
||||
* @param annotationField 注解属性(字段)名称 |
||||
* @param value 要更新的属性值 |
||||
* @since 5.5.2 |
||||
*/ |
||||
@SuppressWarnings({"rawtypes", "unchecked"}) |
||||
public static void setValue(Annotation annotation, String annotationField, Object value) { |
||||
final Map memberValues = (Map) ReflectUtil.getFieldValue(Proxy.getInvocationHandler(annotation), "memberValues"); |
||||
memberValues.put(annotationField, value); |
||||
} |
||||
|
||||
/** |
||||
* 该注解对象是否为通过代理类生成的合成注解 |
||||
* |
||||
* @param annotation 注解对象 |
||||
* @return 是否 |
||||
* @see SynthesizedAnnotationProxy#isProxyAnnotation(Class) |
||||
*/ |
||||
public static boolean isSynthesizedAnnotation(Annotation annotation) { |
||||
return SynthesizedAnnotationProxy.isProxyAnnotation(annotation.getClass()); |
||||
} |
||||
|
||||
/** |
||||
* 获取别名支持后的注解 |
||||
* |
||||
* @param annotationEle 被注解的类 |
||||
* @param annotationType 注解类型Class |
||||
* @param <T> 注解类型 |
||||
* @return 别名支持后的注解 |
||||
* @since 5.7.23 |
||||
*/ |
||||
public static <T extends Annotation> T getAnnotationAlias(AnnotatedElement annotationEle, Class<T> annotationType) { |
||||
final T annotation = getAnnotation(annotationEle, annotationType); |
||||
return aggregatingFromAnnotation(annotation).synthesize(annotationType); |
||||
} |
||||
|
||||
/** |
||||
* 将指定注解实例与其元注解转为合成注解 |
||||
* |
||||
* @param annotationType 注解类 |
||||
* @param annotations 注解对象 |
||||
* @param <T> 注解类型 |
||||
* @return 合成注解 |
||||
* @see SynthesizedAggregateAnnotation |
||||
*/ |
||||
public static <T extends Annotation> T getSynthesizedAnnotation(Class<T> annotationType, Annotation... annotations) { |
||||
// TODO 缓存合成注解信息,避免重复解析
|
||||
return Opt.ofNullable(annotations) |
||||
.filter(ArrayUtil::isNotEmpty) |
||||
.map(AnnotationUtil::aggregatingFromAnnotationWithMeta) |
||||
.map(a -> a.synthesize(annotationType)) |
||||
.get(); |
||||
} |
||||
|
||||
/** |
||||
* <p>获取元素上距离指定元素最接近的合成注解 |
||||
* <ul> |
||||
* <li>若元素是类,则递归解析全部父类和全部父接口上的注解;</li> |
||||
* <li>若元素是方法、属性或注解,则只解析其直接声明的注解;</li> |
||||
* </ul> |
||||
* |
||||
* <p>注解合成规则如下: |
||||
* 若{@code AnnotatedEle}按顺序从上到下声明了A,B,C三个注解,且三注解存在元注解如下: |
||||
* <pre> |
||||
* A -> M3 |
||||
* B -> M1 -> M2 -> M3 |
||||
* C -> M2 -> M3 |
||||
* </pre> |
||||
* 此时入参{@code annotationType}类型为{@code M2},则最终将优先返回基于根注解B合成的合成注解 |
||||
* |
||||
* @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission |
||||
* @param annotationType 注解类 |
||||
* @param <T> 注解类型 |
||||
* @return 合成注解 |
||||
* @see SynthesizedAggregateAnnotation |
||||
*/ |
||||
public static <T extends Annotation> T getSynthesizedAnnotation(AnnotatedElement annotatedEle, Class<T> annotationType) { |
||||
T target = annotatedEle.getAnnotation(annotationType); |
||||
if (ObjectUtil.isNotNull(target)) { |
||||
return target; |
||||
} |
||||
return AnnotationScanner.DIRECTLY |
||||
.getAnnotationsIfSupport(annotatedEle).stream() |
||||
.map(annotation -> getSynthesizedAnnotation(annotationType, annotation)) |
||||
.filter(Objects::nonNull) |
||||
.findFirst() |
||||
.orElse(null); |
||||
} |
||||
|
||||
/** |
||||
* 获取元素上所有指定注解 |
||||
* <ul> |
||||
* <li>若元素是类,则递归解析全部父类和全部父接口上的注解;</li> |
||||
* <li>若元素是方法、属性或注解,则只解析其直接声明的注解;</li> |
||||
* </ul> |
||||
* |
||||
* <p>注解合成规则如下: |
||||
* 若{@code AnnotatedEle}按顺序从上到下声明了A,B,C三个注解,且三注解存在元注解如下: |
||||
* <pre> |
||||
* A -> M1 -> M2 |
||||
* B -> M3 -> M1 -> M2 |
||||
* C -> M2 |
||||
* </pre> |
||||
* 此时入参{@code annotationType}类型为{@code M1},则最终将返回基于根注解A与根注解B合成的合成注解。 |
||||
* |
||||
* @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission |
||||
* @param annotationType 注解类 |
||||
* @param <T> 注解类型 |
||||
* @return 合成注解 |
||||
* @see SynthesizedAggregateAnnotation |
||||
*/ |
||||
public static <T extends Annotation> List<T> getAllSynthesizedAnnotations(AnnotatedElement annotatedEle, Class<T> annotationType) { |
||||
return AnnotationScanner.DIRECTLY |
||||
.getAnnotationsIfSupport(annotatedEle).stream() |
||||
.map(annotation -> getSynthesizedAnnotation(annotationType, annotation)) |
||||
.filter(Objects::nonNull) |
||||
.collect(Collectors.toList()); |
||||
} |
||||
|
||||
/** |
||||
* 对指定注解对象进行聚合 |
||||
* |
||||
* @param annotations 注解对象 |
||||
* @return 聚合注解 |
||||
*/ |
||||
public static SynthesizedAggregateAnnotation aggregatingFromAnnotation(Annotation... annotations) { |
||||
return new GenericSynthesizedAggregateAnnotation(Arrays.asList(annotations), AnnotationScanner.NOTHING); |
||||
} |
||||
|
||||
/** |
||||
* 对指定注解对象及其元注解进行聚合 |
||||
* |
||||
* @param annotations 注解对象 |
||||
* @return 聚合注解 |
||||
*/ |
||||
public static SynthesizedAggregateAnnotation aggregatingFromAnnotationWithMeta(Annotation... annotations) { |
||||
return new GenericSynthesizedAggregateAnnotation(Arrays.asList(annotations), AnnotationScanner.DIRECTLY_AND_META_ANNOTATION); |
||||
} |
||||
|
||||
/** |
||||
* 方法是否为注解属性方法。 <br> |
||||
* 方法无参数,且有返回值的方法认为是注解属性的方法。 |
||||
* |
||||
* @param method 方法 |
||||
*/ |
||||
static boolean isAttributeMethod(Method method) { |
||||
return method.getParameterCount() == 0 && method.getReturnType() != void.class; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,63 @@ |
||||
package cn.hutool.core.annotation; |
||||
|
||||
import cn.hutool.core.lang.Assert; |
||||
import cn.hutool.core.util.ObjectUtil; |
||||
import cn.hutool.core.util.ReflectUtil; |
||||
|
||||
import java.lang.annotation.Annotation; |
||||
import java.lang.reflect.Method; |
||||
|
||||
/** |
||||
* {@link AnnotationAttribute}的基本实现 |
||||
* |
||||
* @author huangchengxing |
||||
*/ |
||||
public class CacheableAnnotationAttribute implements AnnotationAttribute { |
||||
|
||||
private boolean valueInvoked; |
||||
private Object value; |
||||
|
||||
private boolean defaultValueInvoked; |
||||
private Object defaultValue; |
||||
|
||||
private final Annotation annotation; |
||||
private final Method attribute; |
||||
|
||||
public CacheableAnnotationAttribute(Annotation annotation, Method attribute) { |
||||
Assert.notNull(annotation, "annotation must not null"); |
||||
Assert.notNull(attribute, "attribute must not null"); |
||||
this.annotation = annotation; |
||||
this.attribute = attribute; |
||||
this.valueInvoked = false; |
||||
this.defaultValueInvoked = false; |
||||
} |
||||
|
||||
@Override |
||||
public Annotation getAnnotation() { |
||||
return this.annotation; |
||||
} |
||||
|
||||
@Override |
||||
public Method getAttribute() { |
||||
return this.attribute; |
||||
} |
||||
|
||||
@Override |
||||
public Object getValue() { |
||||
if (!valueInvoked) { |
||||
valueInvoked = true; |
||||
value = ReflectUtil.invoke(annotation, attribute); |
||||
} |
||||
return value; |
||||
} |
||||
|
||||
@Override |
||||
public boolean isValueEquivalentToDefaultValue() { |
||||
if (!defaultValueInvoked) { |
||||
defaultValue = attribute.getDefaultValue(); |
||||
defaultValueInvoked = true; |
||||
} |
||||
return ObjectUtil.equals(getValue(), defaultValue); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,62 @@ |
||||
package cn.hutool.core.annotation; |
||||
|
||||
import cn.hutool.core.lang.Assert; |
||||
import cn.hutool.core.map.multi.RowKeyTable; |
||||
import cn.hutool.core.map.multi.Table; |
||||
import cn.hutool.core.util.ObjectUtil; |
||||
|
||||
import java.util.Collection; |
||||
import java.util.Comparator; |
||||
|
||||
/** |
||||
* <p>带缓存功能的{@link SynthesizedAnnotationAttributeProcessor}实现, |
||||
* 构建时需要传入比较器,获取属性值时将根据比较器对合成注解进行排序, |
||||
* 然后选择具有所需属性的,排序最靠前的注解用于获取属性值 |
||||
* |
||||
* <p>通过该处理器获取合成注解属性值时会出现隐式别名, |
||||
* 即子注解和元注解中同时存在类型和名称皆相同的属性时,元注解中属性总是会被该属性覆盖, |
||||
* 并且该覆盖关系并不会通过{@link Alias}或{@link Link}被传递到关联的属性中。 |
||||
* |
||||
* @author huangchengxing |
||||
*/ |
||||
public class CacheableSynthesizedAnnotationAttributeProcessor implements SynthesizedAnnotationAttributeProcessor { |
||||
|
||||
private final Table<String, Class<?>, Object> valueCaches = new RowKeyTable<>(); |
||||
private final Comparator<Hierarchical> annotationComparator; |
||||
|
||||
/** |
||||
* 创建一个带缓存的注解值选择器 |
||||
* |
||||
* @param annotationComparator 注解比较器,排序更靠前的注解将被优先用于获取值 |
||||
*/ |
||||
public CacheableSynthesizedAnnotationAttributeProcessor(Comparator<Hierarchical> annotationComparator) { |
||||
Assert.notNull(annotationComparator, "annotationComparator must not null"); |
||||
this.annotationComparator = annotationComparator; |
||||
} |
||||
|
||||
/** |
||||
* 创建一个带缓存的注解值选择器, |
||||
* 默认按{@link SynthesizedAnnotation#getVerticalDistance()}和{@link SynthesizedAnnotation#getHorizontalDistance()}排序, |
||||
* 越靠前的越优先被取值。 |
||||
*/ |
||||
public CacheableSynthesizedAnnotationAttributeProcessor() { |
||||
this(Hierarchical.DEFAULT_HIERARCHICAL_COMPARATOR); |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
@Override |
||||
public <T> T getAttributeValue(String attributeName, Class<T> attributeType, Collection<? extends SynthesizedAnnotation> synthesizedAnnotations) { |
||||
Object value = valueCaches.get(attributeName, attributeType); |
||||
// 此处理论上不可能出现缓存值为nul的情况
|
||||
if (ObjectUtil.isNotNull(value)) { |
||||
return (T)value; |
||||
} |
||||
value = synthesizedAnnotations.stream() |
||||
.filter(ma -> ma.hasAttribute(attributeName, attributeType)) |
||||
.min(annotationComparator) |
||||
.map(ma -> ma.getAttributeValue(attributeName)) |
||||
.orElse(null); |
||||
valueCaches.put(attributeName, attributeType, value); |
||||
return (T)value; |
||||
} |
||||
} |
@ -0,0 +1,165 @@ |
||||
package cn.hutool.core.annotation; |
||||
|
||||
import cn.hutool.core.map.TableMap; |
||||
|
||||
import java.io.Serializable; |
||||
import java.lang.annotation.Annotation; |
||||
import java.lang.reflect.AnnotatedElement; |
||||
import java.util.Arrays; |
||||
import java.util.Collection; |
||||
import java.util.Map; |
||||
import java.util.function.Predicate; |
||||
|
||||
/** |
||||
* 组合注解 对JDK的原生注解机制做一个增强,支持类似Spring的组合注解。<br> |
||||
* 核心实现使用了递归获取指定元素上的注解以及注解的注解,以实现复合注解的获取。 |
||||
* |
||||
* @author Succy, Looly |
||||
* @since 4.0.9 |
||||
**/ |
||||
|
||||
public class CombinationAnnotationElement implements AnnotatedElement, Serializable { |
||||
private static final long serialVersionUID = 1L; |
||||
|
||||
/** |
||||
* 创建CombinationAnnotationElement |
||||
* |
||||
* @param element 需要解析注解的元素:可以是Class、Method、Field、Constructor、ReflectPermission |
||||
* @param predicate 过滤器,{@link Predicate#test(Object)}返回{@code true}保留,否则不保留 |
||||
* @return CombinationAnnotationElement |
||||
* @since 5.8.0 |
||||
*/ |
||||
public static CombinationAnnotationElement of(AnnotatedElement element, Predicate<Annotation> predicate) { |
||||
return new CombinationAnnotationElement(element, predicate); |
||||
} |
||||
|
||||
/** |
||||
* 注解类型与注解对象对应表 |
||||
*/ |
||||
private Map<Class<? extends Annotation>, Annotation> annotationMap; |
||||
/** |
||||
* 直接注解类型与注解对象对应表 |
||||
*/ |
||||
private Map<Class<? extends Annotation>, Annotation> declaredAnnotationMap; |
||||
/** |
||||
* 过滤器 |
||||
*/ |
||||
private final Predicate<Annotation> predicate; |
||||
|
||||
/** |
||||
* 构造 |
||||
* |
||||
* @param element 需要解析注解的元素:可以是Class、Method、Field、Constructor、ReflectPermission |
||||
*/ |
||||
public CombinationAnnotationElement(AnnotatedElement element) { |
||||
this(element, null); |
||||
} |
||||
|
||||
/** |
||||
* 构造 |
||||
* |
||||
* @param element 需要解析注解的元素:可以是Class、Method、Field、Constructor、ReflectPermission |
||||
* @param predicate 过滤器,{@link Predicate#test(Object)}返回{@code true}保留,否则不保留 |
||||
* @since 5.8.0 |
||||
*/ |
||||
public CombinationAnnotationElement(AnnotatedElement element, Predicate<Annotation> predicate) { |
||||
this.predicate = predicate; |
||||
init(element); |
||||
} |
||||
|
||||
@Override |
||||
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) { |
||||
return annotationMap.containsKey(annotationClass); |
||||
} |
||||
|
||||
@Override |
||||
@SuppressWarnings("unchecked") |
||||
public <T extends Annotation> T getAnnotation(Class<T> annotationClass) { |
||||
Annotation annotation = annotationMap.get(annotationClass); |
||||
return (annotation == null) ? null : (T) annotation; |
||||
} |
||||
|
||||
@Override |
||||
public Annotation[] getAnnotations() { |
||||
final Collection<Annotation> annotations = this.annotationMap.values(); |
||||
return annotations.toArray(new Annotation[0]); |
||||
} |
||||
|
||||
@Override |
||||
public Annotation[] getDeclaredAnnotations() { |
||||
final Collection<Annotation> annotations = this.declaredAnnotationMap.values(); |
||||
return annotations.toArray(new Annotation[0]); |
||||
} |
||||
|
||||
/** |
||||
* 初始化 |
||||
* |
||||
* @param element 元素 |
||||
*/ |
||||
private void init(AnnotatedElement element) { |
||||
final Annotation[] declaredAnnotations = element.getDeclaredAnnotations(); |
||||
this.declaredAnnotationMap = new TableMap<>(); |
||||
parseDeclared(declaredAnnotations); |
||||
|
||||
final Annotation[] annotations = element.getAnnotations(); |
||||
if (Arrays.equals(declaredAnnotations, annotations)) { |
||||
this.annotationMap = this.declaredAnnotationMap; |
||||
} else { |
||||
this.annotationMap = new TableMap<>(); |
||||
parse(annotations); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 进行递归解析注解,直到全部都是元注解为止 |
||||
* |
||||
* @param annotations Class, Method, Field等 |
||||
*/ |
||||
private void parseDeclared(Annotation[] annotations) { |
||||
Class<? extends Annotation> annotationType; |
||||
// 直接注解
|
||||
for (Annotation annotation : annotations) { |
||||
annotationType = annotation.annotationType(); |
||||
// issue#I5FQGW@Gitee:跳过元注解和已经处理过的注解,防止递归调用
|
||||
if (AnnotationUtil.isNotJdkMateAnnotation(annotationType) |
||||
&& !declaredAnnotationMap.containsKey(annotationType)) { |
||||
if(test(annotation)){ |
||||
declaredAnnotationMap.put(annotationType, annotation); |
||||
} |
||||
// 测试不通过的注解,不影响继续递归
|
||||
parseDeclared(annotationType.getDeclaredAnnotations()); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 进行递归解析注解,直到全部都是元注解为止 |
||||
* |
||||
* @param annotations Class, Method, Field等 |
||||
*/ |
||||
private void parse(Annotation[] annotations) { |
||||
Class<? extends Annotation> annotationType; |
||||
for (Annotation annotation : annotations) { |
||||
annotationType = annotation.annotationType(); |
||||
// issue#I5FQGW@Gitee:跳过元注解和已经处理过的注解,防止递归调用
|
||||
if (AnnotationUtil.isNotJdkMateAnnotation(annotationType) |
||||
&& !declaredAnnotationMap.containsKey(annotationType)) { |
||||
if(test(annotation)){ |
||||
annotationMap.put(annotationType, annotation); |
||||
} |
||||
// 测试不通过的注解,不影响继续递归
|
||||
parse(annotationType.getAnnotations()); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 检查给定的注解是否符合过滤条件 |
||||
* |
||||
* @param annotation 注解对象 |
||||
* @return 是否符合条件 |
||||
*/ |
||||
private boolean test(Annotation annotation) { |
||||
return null == this.predicate || this.predicate.test(annotation); |
||||
} |
||||
} |
@ -0,0 +1,35 @@ |
||||
package cn.hutool.core.annotation; |
||||
|
||||
import java.lang.annotation.*; |
||||
|
||||
/** |
||||
* <p>{@link Link}的子注解。表示“原始属性”将强制作为“关联属性”的别名。效果等同于在“原始属性”上添加{@link Alias}注解, |
||||
* 任何情况下,获取“关联属性”的值都将直接返回“原始属性”的值 |
||||
* <b>注意,该注解与{@link Link}、{@link AliasFor}或{@link MirrorFor}一起使用时,将只有被声明在最上面的注解会生效</b> |
||||
* |
||||
* @author huangchengxing |
||||
* @see Link |
||||
* @see RelationType#FORCE_ALIAS_FOR |
||||
*/ |
||||
@Link(type = RelationType.FORCE_ALIAS_FOR) |
||||
@Documented |
||||
@Retention(RetentionPolicy.RUNTIME) |
||||
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) |
||||
public @interface ForceAliasFor { |
||||
|
||||
/** |
||||
* 产生关联的注解类型,当不指定时,默认指注释的属性所在的类 |
||||
* |
||||
* @return 关联注解类型 |
||||
*/ |
||||
@Link(annotation = Link.class, attribute = "annotation", type = RelationType.FORCE_ALIAS_FOR) |
||||
Class<? extends Annotation> annotation() default Annotation.class; |
||||
|
||||
/** |
||||
* {@link #annotation()}指定注解中关联的属性 |
||||
* |
||||
* @return 关联的属性 |
||||
*/ |
||||
@Link(annotation = Link.class, attribute = "attribute", type = RelationType.FORCE_ALIAS_FOR) |
||||
String attribute() default ""; |
||||
} |
@ -0,0 +1,49 @@ |
||||
package cn.hutool.core.annotation; |
||||
|
||||
/** |
||||
* 表示一个被指定了强制别名的注解属性。 |
||||
* 当调用{@link #getValue()}时,总是返回{@link #linked}的值 |
||||
* |
||||
* @author huangchengxing |
||||
* @see AliasAnnotationPostProcessor |
||||
* @see AliasLinkAnnotationPostProcessor |
||||
* @see RelationType#ALIAS_FOR |
||||
* @see RelationType#FORCE_ALIAS_FOR |
||||
*/ |
||||
public class ForceAliasedAnnotationAttribute extends AbstractWrappedAnnotationAttribute { |
||||
|
||||
protected ForceAliasedAnnotationAttribute(AnnotationAttribute origin, AnnotationAttribute linked) { |
||||
super(origin, linked); |
||||
} |
||||
|
||||
/** |
||||
* 总是返回{@link #linked}的{@link AnnotationAttribute#getValue()}的返回值 |
||||
* |
||||
* @return {@link #linked}的{@link AnnotationAttribute#getValue()}的返回值 |
||||
*/ |
||||
@Override |
||||
public Object getValue() { |
||||
return linked.getValue(); |
||||
} |
||||
|
||||
/** |
||||
* 总是返回{@link #linked}的{@link AnnotationAttribute#isValueEquivalentToDefaultValue()}的返回值 |
||||
* |
||||
* @return {@link #linked}的{@link AnnotationAttribute#isValueEquivalentToDefaultValue()}的返回值 |
||||
*/ |
||||
@Override |
||||
public boolean isValueEquivalentToDefaultValue() { |
||||
return linked.isValueEquivalentToDefaultValue(); |
||||
} |
||||
|
||||
/** |
||||
* 总是返回{@link #linked}的{@link AnnotationAttribute#getAttributeType()}的返回值 |
||||
* |
||||
* @return {@link #linked}的{@link AnnotationAttribute#getAttributeType()}的返回值 |
||||
*/ |
||||
@Override |
||||
public Class<?> getAttributeType() { |
||||
return linked.getAttributeType(); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,318 @@ |
||||
package cn.hutool.core.annotation; |
||||
|
||||
import cn.hutool.core.annotation.scanner.AnnotationScanner; |
||||
import cn.hutool.core.annotation.scanner.MetaAnnotationScanner; |
||||
import cn.hutool.core.lang.Assert; |
||||
import cn.hutool.core.lang.Opt; |
||||
import cn.hutool.core.util.ObjectUtil; |
||||
|
||||
import java.lang.annotation.Annotation; |
||||
import java.lang.reflect.AnnotatedElement; |
||||
import java.util.*; |
||||
|
||||
/** |
||||
* {@link SynthesizedAggregateAnnotation}的基本实现,表示基于多个注解对象, |
||||
* 或多个根注解对象与他们的多层元注解对象的聚合得到的注解。 |
||||
* |
||||
* <p>假设现有注解A,若指定的{@link #annotationScanner}支持扫描注解A的元注解, |
||||
* 且A上存在元注解B,B上存在元注解C,则对注解A进行解析,将得到包含根注解A,以及其元注解B、C在内的合成元注解聚合{@link GenericSynthesizedAggregateAnnotation}。 |
||||
* 从{@link AnnotatedElement}的角度来说,得到的合成注解是一个同时承载有ABC三个注解对象的被注解元素, |
||||
* 因此通过调用{@link AnnotatedElement}的相关方法将返回对应符合语义的注解对象。 |
||||
* |
||||
* <p>在扫描指定根注解及其元注解时,若在不同的层级出现了类型相同的注解实例, |
||||
* 将会根据实例化时指定的{@link SynthesizedAnnotationSelector}选择最优的注解, |
||||
* 完成对根注解及其元注解的扫描后,合成注解中每种类型的注解对象都将有且仅有一个。<br> |
||||
* 默认情况下,将使用{@link SynthesizedAnnotationSelector#NEAREST_AND_OLDEST_PRIORITY}作为选择器, |
||||
* 此时若出现扫描时得到了多个同类型的注解对象,有且仅有最接近根注解的注解对象会被作为有效注解。 |
||||
* |
||||
* <p>当扫描的注解对象经过{@link SynthesizedAnnotationSelector}处理后, |
||||
* 将会被转为{@link MetaAnnotation},并使用在实例化时指定的{@link AliasAnnotationPostProcessor} |
||||
* 进行后置处理。<br> |
||||
* 默认情况下,将注册以下后置处理器以对{@link Alias}与{@link Link}和其扩展注解提供支持: |
||||
* <ul> |
||||
* <li>{@link AliasAnnotationPostProcessor};</li> |
||||
* <li>{@link MirrorLinkAnnotationPostProcessor};</li> |
||||
* <li>{@link AliasLinkAnnotationPostProcessor};</li> |
||||
* </ul> |
||||
* 若用户需要自行扩展,则需要保证上述三个处理器被正确注入当前实例。 |
||||
* |
||||
* <p>{@link GenericSynthesizedAggregateAnnotation}支持通过{@link #getAttributeValue(String, Class)}, |
||||
* 或通过{@link #synthesize(Class)}获得注解代理对象后获取指定类型的注解属性值, |
||||
* 返回的属性值将根据合成注解中对应原始注解属性上的{@link Alias}与{@link Link}注解而有所变化。 |
||||
* 通过当前实例获取属性值时,将经过{@link SynthesizedAnnotationAttributeProcessor}的处理。<br> |
||||
* 默认情况下,实例将会注册{@link CacheableSynthesizedAnnotationAttributeProcessor}, |
||||
* 该处理器将令元注解中与子注解类型与名称皆一致的属性被子注解的属性覆盖,并且缓存最终获取到的属性值。 |
||||
* |
||||
* @author huangchengxing |
||||
* @see AnnotationUtil |
||||
* @see SynthesizedAnnotationProxy |
||||
* @see SynthesizedAnnotationSelector |
||||
* @see SynthesizedAnnotationAttributeProcessor |
||||
* @see SynthesizedAnnotationPostProcessor |
||||
* @see AnnotationSynthesizer |
||||
* @see AnnotationScanner |
||||
*/ |
||||
public class GenericSynthesizedAggregateAnnotation |
||||
extends AbstractAnnotationSynthesizer<List<Annotation>> |
||||
implements SynthesizedAggregateAnnotation { |
||||
|
||||
/** |
||||
* 根对象 |
||||
*/ |
||||
private final Object root; |
||||
|
||||
/** |
||||
* 距离根对象的垂直距离 |
||||
*/ |
||||
private final int verticalDistance; |
||||
|
||||
/** |
||||
* 距离根对象的水平距离 |
||||
*/ |
||||
private final int horizontalDistance; |
||||
|
||||
/** |
||||
* 合成注解属性处理器 |
||||
*/ |
||||
private final SynthesizedAnnotationAttributeProcessor attributeProcessor; |
||||
|
||||
/** |
||||
* 基于指定根注解,为其与其元注解的层级结构中的全部注解构造一个合成注解。 |
||||
* 当层级结构中出现了相同的注解对象时,将优先选择以距离根注解最近,且优先被扫描的注解对象, |
||||
* 当获取值时,同样遵循该规则。 |
||||
* |
||||
* @param source 源注解 |
||||
*/ |
||||
public GenericSynthesizedAggregateAnnotation(Annotation... source) { |
||||
this(Arrays.asList(source), new MetaAnnotationScanner()); |
||||
} |
||||
|
||||
/** |
||||
* 基于指定根注解,为其层级结构中的全部注解构造一个合成注解。 |
||||
* 若扫描器支持对注解的层级结构进行扫描,则若层级结构中出现了相同的注解对象时, |
||||
* 将优先选择以距离根注解最近,且优先被扫描的注解对象,并且当获取注解属性值时同样遵循该规则。 |
||||
* |
||||
* @param source 源注解 |
||||
* @param annotationScanner 注解扫描器,该扫描器必须支持扫描注解类 |
||||
*/ |
||||
public GenericSynthesizedAggregateAnnotation(List<Annotation> source, AnnotationScanner annotationScanner) { |
||||
this( |
||||
source, SynthesizedAnnotationSelector.NEAREST_AND_OLDEST_PRIORITY, |
||||
new CacheableSynthesizedAnnotationAttributeProcessor(), |
||||
Arrays.asList( |
||||
SynthesizedAnnotationPostProcessor.ALIAS_ANNOTATION_POST_PROCESSOR, |
||||
SynthesizedAnnotationPostProcessor.MIRROR_LINK_ANNOTATION_POST_PROCESSOR, |
||||
SynthesizedAnnotationPostProcessor.ALIAS_LINK_ANNOTATION_POST_PROCESSOR |
||||
), |
||||
annotationScanner |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* 基于指定根注解,为其层级结构中的全部注解构造一个合成注解 |
||||
* |
||||
* @param source 当前查找的注解对象 |
||||
* @param annotationSelector 合成注解选择器 |
||||
* @param attributeProcessor 注解属性处理器 |
||||
* @param annotationPostProcessors 注解后置处理器 |
||||
* @param annotationScanner 注解扫描器,该扫描器必须支持扫描注解类 |
||||
*/ |
||||
public GenericSynthesizedAggregateAnnotation( |
||||
List<Annotation> source, |
||||
SynthesizedAnnotationSelector annotationSelector, |
||||
SynthesizedAnnotationAttributeProcessor attributeProcessor, |
||||
Collection<SynthesizedAnnotationPostProcessor> annotationPostProcessors, |
||||
AnnotationScanner annotationScanner) { |
||||
this( |
||||
null, 0, 0, |
||||
source, annotationSelector, attributeProcessor, annotationPostProcessors, annotationScanner |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* 基于指定根注解,为其层级结构中的全部注解构造一个合成注解 |
||||
* |
||||
* @param root 根对象 |
||||
* @param verticalDistance 距离根对象的水平距离 |
||||
* @param horizontalDistance 距离根对象的垂直距离 |
||||
* @param source 当前查找的注解对象 |
||||
* @param annotationSelector 合成注解选择器 |
||||
* @param attributeProcessor 注解属性处理器 |
||||
* @param annotationPostProcessors 注解后置处理器 |
||||
* @param annotationScanner 注解扫描器,该扫描器必须支持扫描注解类 |
||||
*/ |
||||
GenericSynthesizedAggregateAnnotation( |
||||
Object root, int verticalDistance, int horizontalDistance, |
||||
List<Annotation> source, |
||||
SynthesizedAnnotationSelector annotationSelector, |
||||
SynthesizedAnnotationAttributeProcessor attributeProcessor, |
||||
Collection<SynthesizedAnnotationPostProcessor> annotationPostProcessors, |
||||
AnnotationScanner annotationScanner) { |
||||
super(source, annotationSelector, annotationPostProcessors, annotationScanner); |
||||
Assert.notNull(attributeProcessor, "attributeProcessor must not null"); |
||||
|
||||
this.root = ObjectUtil.defaultIfNull(root, this); |
||||
this.verticalDistance = verticalDistance; |
||||
this.horizontalDistance = horizontalDistance; |
||||
this.attributeProcessor = attributeProcessor; |
||||
} |
||||
|
||||
/** |
||||
* 获取根对象 |
||||
* |
||||
* @return 根对象 |
||||
*/ |
||||
@Override |
||||
public Object getRoot() { |
||||
return root; |
||||
} |
||||
|
||||
/** |
||||
* 获取与根对象的垂直距离 |
||||
* |
||||
* @return 与根对象的垂直距离 |
||||
*/ |
||||
@Override |
||||
public int getVerticalDistance() { |
||||
return verticalDistance; |
||||
} |
||||
|
||||
/** |
||||
* 获取与根对象的水平距离 |
||||
* |
||||
* @return 获取与根对象的水平距离 |
||||
*/ |
||||
@Override |
||||
public int getHorizontalDistance() { |
||||
return horizontalDistance; |
||||
} |
||||
|
||||
/** |
||||
* 按广度优先扫描{@link #source}上的元注解 |
||||
*/ |
||||
@Override |
||||
protected Map<Class<? extends Annotation>, SynthesizedAnnotation> loadAnnotations() { |
||||
Map<Class<? extends Annotation>, SynthesizedAnnotation> annotationMap = new LinkedHashMap<>(); |
||||
|
||||
// 根注解默认水平坐标为0,根注解的元注解坐标从1开始
|
||||
for (int i = 0; i < source.size(); i++) { |
||||
final Annotation sourceAnnotation = source.get(i); |
||||
Assert.isFalse(AnnotationUtil.isSynthesizedAnnotation(sourceAnnotation), "source [{}] has been synthesized"); |
||||
annotationMap.put(sourceAnnotation.annotationType(), new MetaAnnotation(sourceAnnotation, sourceAnnotation, 0, i)); |
||||
Assert.isTrue( |
||||
annotationScanner.support(sourceAnnotation.annotationType()), |
||||
"annotation scanner [{}] cannot support scan [{}]", |
||||
annotationScanner, sourceAnnotation.annotationType() |
||||
); |
||||
annotationScanner.scan( |
||||
(index, annotation) -> { |
||||
SynthesizedAnnotation oldAnnotation = annotationMap.get(annotation.annotationType()); |
||||
SynthesizedAnnotation newAnnotation = new MetaAnnotation(sourceAnnotation, annotation, index + 1, annotationMap.size()); |
||||
if (ObjectUtil.isNull(oldAnnotation)) { |
||||
annotationMap.put(annotation.annotationType(), newAnnotation); |
||||
} else { |
||||
annotationMap.put(annotation.annotationType(), annotationSelector.choose(oldAnnotation, newAnnotation)); |
||||
} |
||||
}, |
||||
sourceAnnotation.annotationType(), null |
||||
); |
||||
} |
||||
return annotationMap; |
||||
} |
||||
|
||||
/** |
||||
* 获取合成注解属性处理器 |
||||
* |
||||
* @return 合成注解属性处理器 |
||||
*/ |
||||
@Override |
||||
public SynthesizedAnnotationAttributeProcessor getAnnotationAttributeProcessor() { |
||||
return this.attributeProcessor; |
||||
} |
||||
|
||||
/** |
||||
* 根据指定的属性名与属性类型获取对应的属性值,若存在{@link Alias}则获取{@link Alias#value()}指定的别名属性的值 |
||||
* <p>当不同层级的注解之间存在同名同类型属性时,将优先获取更接近根注解的属性 |
||||
* |
||||
* @param attributeName 属性名 |
||||
* @param attributeType 属性类型 |
||||
* @return 属性 |
||||
*/ |
||||
@Override |
||||
public Object getAttributeValue(String attributeName, Class<?> attributeType) { |
||||
return attributeProcessor.getAttributeValue(attributeName, attributeType, synthesizedAnnotationMap.values()); |
||||
} |
||||
|
||||
/** |
||||
* 获取合成注解中包含的指定注解 |
||||
* |
||||
* @param annotationType 注解类型 |
||||
* @param <T> 注解类型 |
||||
* @return 注解对象 |
||||
*/ |
||||
@Override |
||||
public <T extends Annotation> T getAnnotation(Class<T> annotationType) { |
||||
return Opt.ofNullable(annotationType) |
||||
.map(synthesizedAnnotationMap::get) |
||||
.map(SynthesizedAnnotation::getAnnotation) |
||||
.map(annotationType::cast) |
||||
.orElse(null); |
||||
} |
||||
|
||||
/** |
||||
* 当前合成注解中是否存在指定元注解 |
||||
* |
||||
* @param annotationType 注解类型 |
||||
* @return 是否 |
||||
*/ |
||||
@Override |
||||
public boolean isAnnotationPresent(Class<? extends Annotation> annotationType) { |
||||
return synthesizedAnnotationMap.containsKey(annotationType); |
||||
} |
||||
|
||||
/** |
||||
* 获取合成注解中包含的全部注解 |
||||
* |
||||
* @return 注解对象 |
||||
*/ |
||||
@Override |
||||
public Annotation[] getAnnotations() { |
||||
return synthesizedAnnotationMap.values().stream() |
||||
.map(SynthesizedAnnotation::getAnnotation) |
||||
.toArray(Annotation[]::new); |
||||
} |
||||
|
||||
/** |
||||
* 若合成注解在存在指定元注解,则使用动态代理生成一个对应的注解实例 |
||||
* |
||||
* @param annotationType 注解类型 |
||||
* @return 合成注解对象 |
||||
* @see SynthesizedAnnotationProxy#create(Class, AnnotationAttributeValueProvider, SynthesizedAnnotation) |
||||
*/ |
||||
@Override |
||||
public <T extends Annotation> T synthesize(Class<T> annotationType, SynthesizedAnnotation annotation) { |
||||
return SynthesizedAnnotationProxy.create(annotationType, this, annotation); |
||||
} |
||||
|
||||
/** |
||||
* 注解包装类,表示{@link #source}以及{@link #source}所属层级结构中的全部关联注解对象 |
||||
* |
||||
* @author huangchengxing |
||||
*/ |
||||
public static class MetaAnnotation extends GenericSynthesizedAnnotation<Annotation, Annotation> { |
||||
|
||||
/** |
||||
* 创建一个合成注解 |
||||
* |
||||
* @param root 根对象 |
||||
* @param annotation 被合成的注解对象 |
||||
* @param verticalDistance 距离根对象的水平距离 |
||||
* @param horizontalDistance 距离根对象的垂直距离 |
||||
*/ |
||||
protected MetaAnnotation(Annotation root, Annotation annotation, int verticalDistance, int horizontalDistance) { |
||||
super(root, annotation, verticalDistance, horizontalDistance); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
@ -0,0 +1,197 @@ |
||||
package cn.hutool.core.annotation; |
||||
|
||||
import cn.hutool.core.lang.Opt; |
||||
import cn.hutool.core.util.ClassUtil; |
||||
import cn.hutool.core.util.ObjectUtil; |
||||
|
||||
import java.lang.annotation.Annotation; |
||||
import java.lang.reflect.Method; |
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
import java.util.function.UnaryOperator; |
||||
import java.util.stream.Collectors; |
||||
import java.util.stream.Stream; |
||||
|
||||
/** |
||||
* {@link SynthesizedAnnotation}的基本实现 |
||||
* |
||||
* @param <R> 根对象类型 |
||||
* @param <T> 注解类型 |
||||
* @author huangchengxing |
||||
*/ |
||||
public class GenericSynthesizedAnnotation<R, T extends Annotation> implements SynthesizedAnnotation { |
||||
|
||||
private final R root; |
||||
private final T annotation; |
||||
private final Map<String, AnnotationAttribute> attributeMethodCaches; |
||||
private final int verticalDistance; |
||||
private final int horizontalDistance; |
||||
|
||||
/** |
||||
* 创建一个合成注解 |
||||
* |
||||
* @param root 根对象 |
||||
* @param annotation 被合成的注解对象 |
||||
* @param verticalDistance 距离根对象的水平距离 |
||||
* @param horizontalDistance 距离根对象的垂直距离 |
||||
*/ |
||||
protected GenericSynthesizedAnnotation( |
||||
R root, T annotation, int verticalDistance, int horizontalDistance) { |
||||
this.root = root; |
||||
this.annotation = annotation; |
||||
this.verticalDistance = verticalDistance; |
||||
this.horizontalDistance = horizontalDistance; |
||||
this.attributeMethodCaches = new HashMap<>(); |
||||
this.attributeMethodCaches.putAll(loadAttributeMethods()); |
||||
} |
||||
|
||||
/** |
||||
* 加载注解属性 |
||||
* |
||||
* @return 注解属性 |
||||
*/ |
||||
protected Map<String, AnnotationAttribute> loadAttributeMethods() { |
||||
return Stream.of(ClassUtil.getDeclaredMethods(annotation.annotationType())) |
||||
.filter(AnnotationUtil::isAttributeMethod) |
||||
.collect(Collectors.toMap(Method::getName, method -> new CacheableAnnotationAttribute(annotation, method))); |
||||
} |
||||
|
||||
/** |
||||
* 元注解是否存在该属性 |
||||
* |
||||
* @param attributeName 属性名 |
||||
* @return 是否存在该属性 |
||||
*/ |
||||
public boolean hasAttribute(String attributeName) { |
||||
return attributeMethodCaches.containsKey(attributeName); |
||||
} |
||||
|
||||
/** |
||||
* 元注解是否存在该属性,且该属性的值类型是指定类型或其子类 |
||||
* |
||||
* @param attributeName 属性名 |
||||
* @param returnType 返回值类型 |
||||
* @return 是否存在该属性 |
||||
*/ |
||||
@Override |
||||
public boolean hasAttribute(String attributeName, Class<?> returnType) { |
||||
return Opt.ofNullable(attributeMethodCaches.get(attributeName)) |
||||
.filter(method -> ClassUtil.isAssignable(returnType, method.getAttributeType())) |
||||
.isPresent(); |
||||
} |
||||
|
||||
/** |
||||
* 获取该注解的全部属性 |
||||
* |
||||
* @return 注解属性 |
||||
*/ |
||||
@Override |
||||
public Map<String, AnnotationAttribute> getAttributes() { |
||||
return this.attributeMethodCaches; |
||||
} |
||||
|
||||
/** |
||||
* 设置属性值 |
||||
* |
||||
* @param attributeName 属性名称 |
||||
* @param attribute 注解属性 |
||||
*/ |
||||
@Override |
||||
public void setAttribute(String attributeName, AnnotationAttribute attribute) { |
||||
attributeMethodCaches.put(attributeName, attribute); |
||||
} |
||||
|
||||
/** |
||||
* 替换属性值 |
||||
* |
||||
* @param attributeName 属性名 |
||||
* @param operator 替换操作 |
||||
*/ |
||||
@Override |
||||
public void replaceAttribute(String attributeName, UnaryOperator<AnnotationAttribute> operator) { |
||||
AnnotationAttribute old = attributeMethodCaches.get(attributeName); |
||||
if (ObjectUtil.isNotNull(old)) { |
||||
attributeMethodCaches.put(attributeName, operator.apply(old)); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 获取属性值 |
||||
* |
||||
* @param attributeName 属性名 |
||||
* @return 属性值 |
||||
*/ |
||||
@Override |
||||
public Object getAttributeValue(String attributeName) { |
||||
return Opt.ofNullable(attributeMethodCaches.get(attributeName)) |
||||
.map(AnnotationAttribute::getValue) |
||||
.get(); |
||||
} |
||||
|
||||
/** |
||||
* 获取该合成注解对应的根节点 |
||||
* |
||||
* @return 合成注解对应的根节点 |
||||
*/ |
||||
@Override |
||||
public R getRoot() { |
||||
return root; |
||||
} |
||||
|
||||
/** |
||||
* 获取被合成的注解对象 |
||||
* |
||||
* @return 注解对象 |
||||
*/ |
||||
@Override |
||||
public T getAnnotation() { |
||||
return annotation; |
||||
} |
||||
|
||||
/** |
||||
* 获取该合成注解与根对象的垂直距离。 |
||||
* 默认情况下,该距离即为当前注解与根对象之间相隔的层级数。 |
||||
* |
||||
* @return 合成注解与根对象的垂直距离 |
||||
*/ |
||||
@Override |
||||
public int getVerticalDistance() { |
||||
return verticalDistance; |
||||
} |
||||
|
||||
/** |
||||
* 获取该合成注解与根对象的水平距离。 |
||||
* 默认情况下,该距离即为当前注解与根对象之间相隔的已经被扫描到的注解数。 |
||||
* |
||||
* @return 合成注解与根对象的水平距离 |
||||
*/ |
||||
@Override |
||||
public int getHorizontalDistance() { |
||||
return horizontalDistance; |
||||
} |
||||
|
||||
/** |
||||
* 获取被合成的注解类型 |
||||
* |
||||
* @return 被合成的注解类型 |
||||
*/ |
||||
@Override |
||||
public Class<? extends Annotation> annotationType() { |
||||
return annotation.annotationType(); |
||||
} |
||||
|
||||
/** |
||||
* 获取注解属性值 |
||||
* |
||||
* @param attributeName 属性名称 |
||||
* @param attributeType 属性类型 |
||||
* @return 注解属性值 |
||||
*/ |
||||
@Override |
||||
public Object getAttributeValue(String attributeName, Class<?> attributeType) { |
||||
return Opt.ofNullable(attributeMethodCaches.get(attributeName)) |
||||
.filter(method -> ClassUtil.isAssignable(attributeType, method.getAttributeType())) |
||||
.map(AnnotationAttribute::getValue) |
||||
.get(); |
||||
} |
||||
} |
@ -0,0 +1,155 @@ |
||||
package cn.hutool.core.annotation; |
||||
|
||||
|
||||
import java.util.Comparator; |
||||
|
||||
/** |
||||
* <p>描述以一个参照物为对象,存在于该参照物的层级结构中的对象。 |
||||
* |
||||
* <p>该对象可通过{@link #getVerticalDistance()}与{@link #getHorizontalDistance()} |
||||
* 描述其在以参照物为基点的坐标坐标系中的位置。<br> |
||||
* 在需要对该接口的实现类进行按优先级排序时,距离{@link #getRoot()}对象越近,则该实现类的优先级越高。 |
||||
* 默认提供了{@link #DEFAULT_HIERARCHICAL_COMPARATOR}用于实现该比较规则。<br> |
||||
* 一般情况下,{@link #getRoot()}返回值相同的对象之间的比较才有意义。 |
||||
* |
||||
* <p>此外,还提供了{@link Selector}接口用于根据一定的规则从两个{@link Hierarchical}实现类中选择并返回一个最合适的对象, |
||||
* 默认提供了四个实现类: |
||||
* <ul> |
||||
* <li>{@link Selector#NEAREST_AND_OLDEST_PRIORITY}: 返回距离根对象更近的对象,当距离一样时优先返回旧对象;</li> |
||||
* <li>{@link Selector#NEAREST_AND_NEWEST_PRIORITY}: 返回距离根对象更近的对象,当距离一样时优先返回新对象;</li> |
||||
* <li>{@link Selector#FARTHEST_AND_OLDEST_PRIORITY}: 返回距离根对象更远的对象,当距离一样时优先返回旧对象;</li> |
||||
* <li>{@link Selector#FARTHEST_AND_NEWEST_PRIORITY}: 返回距离根对象更远的对象,当距离一样时优先返回新对象;</li> |
||||
* </ul> |
||||
* |
||||
* @author huangchengxing |
||||
*/ |
||||
public interface Hierarchical extends Comparable<Hierarchical> { |
||||
|
||||
// ====================== compare ======================
|
||||
|
||||
/** |
||||
* 默认{@link #getHorizontalDistance()}与{@link #getVerticalDistance()}排序的比较器 |
||||
*/ |
||||
Comparator<Hierarchical> DEFAULT_HIERARCHICAL_COMPARATOR = Comparator |
||||
.comparing(Hierarchical::getVerticalDistance) |
||||
.thenComparing(Hierarchical::getHorizontalDistance); |
||||
|
||||
/** |
||||
* 按{@link #getVerticalDistance()}和{@link #getHorizontalDistance()}排序 |
||||
* |
||||
* @param o {@link SynthesizedAnnotation}对象 |
||||
* @return 比较值 |
||||
*/ |
||||
@Override |
||||
default int compareTo(Hierarchical o) { |
||||
return DEFAULT_HIERARCHICAL_COMPARATOR.compare(this, o); |
||||
} |
||||
|
||||
// ====================== hierarchical ======================
|
||||
|
||||
/** |
||||
* 参照物,即坐标为{@code (0, 0)}的对象。 |
||||
* 当对象本身即为参照物时,该方法应当返回其本身 |
||||
* |
||||
* @return 参照物 |
||||
*/ |
||||
Object getRoot(); |
||||
|
||||
/** |
||||
* 获取该对象与参照物的垂直距离。 |
||||
* 默认情况下,该距离即为当前对象与参照物之间相隔的层级数。 |
||||
* |
||||
* @return 合成注解与根对象的垂直距离 |
||||
*/ |
||||
int getVerticalDistance(); |
||||
|
||||
/** |
||||
* 获取该对象与参照物的水平距离。 |
||||
* 默认情况下,该距离即为当前对象在与参照物{@link #getVerticalDistance()}相同的情况下条, |
||||
* 该对象被扫描到的顺序。 |
||||
* |
||||
* @return 合成注解与根对象的水平距离 |
||||
*/ |
||||
int getHorizontalDistance(); |
||||
|
||||
// ====================== selector ======================
|
||||
|
||||
/** |
||||
* {@link Hierarchical}选择器,用于根据一定的规则从两个{@link Hierarchical}实现类中选择并返回一个最合适的对象 |
||||
*/ |
||||
@FunctionalInterface |
||||
interface Selector { |
||||
|
||||
/** |
||||
* 返回距离根对象更近的对象,当距离一样时优先返回旧对象 |
||||
*/ |
||||
Selector NEAREST_AND_OLDEST_PRIORITY = new NearestAndOldestPrioritySelector(); |
||||
|
||||
/** |
||||
* 返回距离根对象更近的对象,当距离一样时优先返回新对象 |
||||
*/ |
||||
Selector NEAREST_AND_NEWEST_PRIORITY = new NearestAndNewestPrioritySelector(); |
||||
|
||||
/** |
||||
* 返回距离根对象更远的对象,当距离一样时优先返回旧对象 |
||||
*/ |
||||
Selector FARTHEST_AND_OLDEST_PRIORITY = new FarthestAndOldestPrioritySelector(); |
||||
|
||||
/** |
||||
* 返回距离根对象更远的对象,当距离一样时优先返回新对象 |
||||
*/ |
||||
Selector FARTHEST_AND_NEWEST_PRIORITY = new FarthestAndNewestPrioritySelector(); |
||||
|
||||
/** |
||||
* 比较两个被合成的对象,选择其中的一个并返回 |
||||
* |
||||
* @param <T> 复合注解类型 |
||||
* @param prev 上一对象,该参数不允许为空 |
||||
* @param next 下一对象,该参数不允许为空 |
||||
* @return 对象 |
||||
*/ |
||||
<T extends Hierarchical> T choose(T prev, T next); |
||||
|
||||
/** |
||||
* 返回距离根对象更近的注解,当距离一样时优先返回旧注解 |
||||
*/ |
||||
class NearestAndOldestPrioritySelector implements Selector { |
||||
@Override |
||||
public <T extends Hierarchical> T choose(T oldAnnotation, T newAnnotation) { |
||||
return newAnnotation.getVerticalDistance() < oldAnnotation.getVerticalDistance() ? newAnnotation : oldAnnotation; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 返回距离根对象更近的注解,当距离一样时优先返回新注解 |
||||
*/ |
||||
class NearestAndNewestPrioritySelector implements Selector { |
||||
@Override |
||||
public <T extends Hierarchical> T choose(T oldAnnotation, T newAnnotation) { |
||||
return newAnnotation.getVerticalDistance() <= oldAnnotation.getVerticalDistance() ? newAnnotation : oldAnnotation; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 返回距离根对象更远的注解,当距离一样时优先返回旧注解 |
||||
*/ |
||||
class FarthestAndOldestPrioritySelector implements Selector { |
||||
@Override |
||||
public <T extends Hierarchical> T choose(T oldAnnotation, T newAnnotation) { |
||||
return newAnnotation.getVerticalDistance() > oldAnnotation.getVerticalDistance() ? newAnnotation : oldAnnotation; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 返回距离根对象更远的注解,当距离一样时优先返回新注解 |
||||
*/ |
||||
class FarthestAndNewestPrioritySelector implements Selector { |
||||
@Override |
||||
public <T extends Hierarchical> T choose(T oldAnnotation, T newAnnotation) { |
||||
return newAnnotation.getVerticalDistance() >= oldAnnotation.getVerticalDistance() ? newAnnotation : oldAnnotation; |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
@ -0,0 +1,49 @@ |
||||
package cn.hutool.core.annotation; |
||||
|
||||
import java.lang.annotation.*; |
||||
|
||||
/** |
||||
* <p>用于在同一注解中,或具有一定关联的不同注解的属性中,表明这些属性之间具有特定的关联关系。 |
||||
* 在通过{@link SynthesizedAggregateAnnotation}获取合成注解后,合成注解获取属性值时会根据该注解进行调整。<br> |
||||
* |
||||
* <p>该注解存在三个字注解:{@link MirrorFor}、{@link ForceAliasFor}或{@link AliasFor}, |
||||
* 使用三个子注解等同于{@link Link}。但是需要注意的是, |
||||
* 当注解中的属性同时存在多个{@link Link}或基于{@link Link}的子注解时, |
||||
* 仅有声明在被注解的属性最上方的注解会生效,其余注解都将被忽略。 |
||||
* |
||||
* <b>注意:该注解的优先级低于{@link Alias}</b> |
||||
* |
||||
* @author huangchengxing |
||||
* @see SynthesizedAggregateAnnotation |
||||
* @see RelationType |
||||
* @see AliasFor |
||||
* @see MirrorFor |
||||
* @see ForceAliasFor |
||||
*/ |
||||
@Documented |
||||
@Retention(RetentionPolicy.RUNTIME) |
||||
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) |
||||
public @interface Link { |
||||
|
||||
/** |
||||
* 产生关联的注解类型,当不指定时,默认指注释的属性所在的类 |
||||
* |
||||
* @return 关联的注解类型 |
||||
*/ |
||||
Class<? extends Annotation> annotation() default Annotation.class; |
||||
|
||||
/** |
||||
* {@link #annotation()}指定注解中关联的属性 |
||||
* |
||||
* @return 属性名 |
||||
*/ |
||||
String attribute() default ""; |
||||
|
||||
/** |
||||
* {@link #attribute()}指定属性与当前注解的属性建的关联关系类型 |
||||
* |
||||
* @return 关系类型 |
||||
*/ |
||||
RelationType type() default RelationType.MIRROR_FOR; |
||||
|
||||
} |
@ -0,0 +1,42 @@ |
||||
package cn.hutool.core.annotation; |
||||
|
||||
import java.lang.annotation.*; |
||||
|
||||
/** |
||||
* <p>{@link Link}的子注解。表示注解的属性与指定的属性互为镜像,通过一个属性将能够获得对方的值。<br> |
||||
* 它们遵循下述规则: |
||||
* <ul> |
||||
* <li>互为镜像的两个属性,必须同时通过指定模式为{@code MIRROR_FOR}的{@link Link}注解指定对方;</li> |
||||
* <li>互为镜像的两个属性,类型必须一致;</li> |
||||
* <li>互为镜像的两个属性在获取值,且两者的值皆不同时,必须且仅允许有一个非默认值,该值被优先返回;</li> |
||||
* <li>互为镜像的两个属性,在值都为默认值或都不为默认值时,两者的值必须相等;</li> |
||||
* </ul> |
||||
* <b>注意,该注解与{@link Link}、{@link ForceAliasFor}或{@link AliasFor}一起使用时,将只有被声明在最上面的注解会生效</b> |
||||
* |
||||
* @author huangchengxing |
||||
* @see Link |
||||
* @see RelationType#MIRROR_FOR |
||||
*/ |
||||
@Link(type = RelationType.MIRROR_FOR) |
||||
@Documented |
||||
@Retention(RetentionPolicy.RUNTIME) |
||||
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) |
||||
public @interface MirrorFor { |
||||
|
||||
/** |
||||
* 产生关联的注解类型,当不指定时,默认指注释的属性所在的类 |
||||
* |
||||
* @return 关联的注解类型 |
||||
*/ |
||||
@Link(annotation = Link.class, attribute = "annotation", type = RelationType.FORCE_ALIAS_FOR) |
||||
Class<? extends Annotation> annotation() default Annotation.class; |
||||
|
||||
/** |
||||
* {@link #annotation()}指定注解中关联的属性 |
||||
* |
||||
* @return 属性名 |
||||
*/ |
||||
@Link(annotation = Link.class, attribute = "attribute", type = RelationType.FORCE_ALIAS_FOR) |
||||
String attribute() default ""; |
||||
|
||||
} |
@ -0,0 +1,132 @@ |
||||
package cn.hutool.core.annotation; |
||||
|
||||
import cn.hutool.core.lang.Assert; |
||||
import cn.hutool.core.text.CharSequenceUtil; |
||||
import cn.hutool.core.util.ObjectUtil; |
||||
|
||||
/** |
||||
* <p>用于处理注解对象中带有{@link Link}注解,且{@link Link#type()}为{@link RelationType#MIRROR_FOR}的属性。<br> |
||||
* 当该处理器执行完毕后,原始合成注解中被{@link Link}注解的属性与{@link Link}注解指向的目标注解的属性, |
||||
* 都将会被被包装并替换为{@link MirroredAnnotationAttribute}。 |
||||
* |
||||
* @author huangchengxing |
||||
* @see RelationType#MIRROR_FOR |
||||
* @see MirroredAnnotationAttribute |
||||
*/ |
||||
public class MirrorLinkAnnotationPostProcessor extends AbstractLinkAnnotationPostProcessor { |
||||
|
||||
private static final RelationType[] PROCESSED_RELATION_TYPES = new RelationType[]{ RelationType.MIRROR_FOR }; |
||||
|
||||
@Override |
||||
public int order() { |
||||
return Integer.MIN_VALUE + 1; |
||||
} |
||||
|
||||
/** |
||||
* 该处理器只处理{@link Link#type()}类型为{@link RelationType#MIRROR_FOR}的注解属性 |
||||
* |
||||
* @return 仅有{@link RelationType#MIRROR_FOR}数组 |
||||
*/ |
||||
@Override |
||||
protected RelationType[] processTypes() { |
||||
return PROCESSED_RELATION_TYPES; |
||||
} |
||||
|
||||
/** |
||||
* 将存在镜像关系的合成注解属性分别包装为{@link MirroredAnnotationAttribute}对象, |
||||
* 并使用包装后{@link MirroredAnnotationAttribute}替换在它们对应合成注解实例中的{@link AnnotationAttribute} |
||||
* |
||||
* @param synthesizer 注解合成器 |
||||
* @param annotation {@code originalAttribute}上的{@link Link}注解对象 |
||||
* @param originalAnnotation 当前正在处理的{@link SynthesizedAnnotation}对象 |
||||
* @param originalAttribute {@code originalAnnotation}上的待处理的属性 |
||||
* @param linkedAnnotation {@link Link}指向的关联注解对象 |
||||
* @param linkedAttribute {@link Link}指向的{@code originalAnnotation}中的关联属性,该参数可能为空 |
||||
*/ |
||||
@Override |
||||
protected void processLinkedAttribute( |
||||
AnnotationSynthesizer synthesizer, Link annotation, |
||||
SynthesizedAnnotation originalAnnotation, AnnotationAttribute originalAttribute, |
||||
SynthesizedAnnotation linkedAnnotation, AnnotationAttribute linkedAttribute) { |
||||
|
||||
// 镜像属性必然成对出现,因此此处必定存在三种情况:
|
||||
// 1.两属性都不为镜像属性,此时继续进行后续处理;
|
||||
// 2.两属性都为镜像属性,并且指向对方,此时无需后续处理;
|
||||
// 3.两属性仅有任意一属性为镜像属性,此时镜像属性必然未指向当前原始属性,此时应该抛出异常;
|
||||
if (originalAttribute instanceof MirroredAnnotationAttribute |
||||
|| linkedAttribute instanceof MirroredAnnotationAttribute) { |
||||
checkMirrored(originalAttribute, linkedAttribute); |
||||
return; |
||||
} |
||||
|
||||
// 校验镜像关系
|
||||
checkMirrorRelation(annotation, originalAttribute, linkedAttribute); |
||||
// 包装这一对镜像属性,并替换原注解中的对应属性
|
||||
final AnnotationAttribute mirroredOriginalAttribute = new MirroredAnnotationAttribute(originalAttribute, linkedAttribute); |
||||
originalAnnotation.setAttribute(originalAttribute.getAttributeName(), mirroredOriginalAttribute); |
||||
final AnnotationAttribute mirroredTargetAttribute = new MirroredAnnotationAttribute(linkedAttribute, originalAttribute); |
||||
linkedAnnotation.setAttribute(annotation.attribute(), mirroredTargetAttribute); |
||||
} |
||||
|
||||
/** |
||||
* 检查映射关系是否正确 |
||||
*/ |
||||
private void checkMirrored(AnnotationAttribute original, AnnotationAttribute mirror) { |
||||
final boolean originalAttributeMirrored = original instanceof MirroredAnnotationAttribute; |
||||
final boolean mirrorAttributeMirrored = mirror instanceof MirroredAnnotationAttribute; |
||||
|
||||
// 校验通过
|
||||
final boolean passed = originalAttributeMirrored && mirrorAttributeMirrored |
||||
&& ObjectUtil.equals(((MirroredAnnotationAttribute)original).getLinked(), ((MirroredAnnotationAttribute)mirror).getOriginal()); |
||||
if (passed) { |
||||
return; |
||||
} |
||||
|
||||
// 校验失败,拼装异常信息用于抛出异常
|
||||
String errorMsg; |
||||
// 原始字段已经跟其他字段形成镜像
|
||||
if (originalAttributeMirrored && !mirrorAttributeMirrored) { |
||||
errorMsg = CharSequenceUtil.format( |
||||
"attribute [{}] cannot mirror for [{}], because it's already mirrored for [{}]", |
||||
original.getAttribute(), mirror.getAttribute(), ((MirroredAnnotationAttribute)original).getLinked() |
||||
); |
||||
} |
||||
// 镜像字段已经跟其他字段形成镜像
|
||||
else if (!originalAttributeMirrored && mirrorAttributeMirrored) { |
||||
errorMsg = CharSequenceUtil.format( |
||||
"attribute [{}] cannot mirror for [{}], because it's already mirrored for [{}]", |
||||
mirror.getAttribute(), original.getAttribute(), ((MirroredAnnotationAttribute)mirror).getLinked() |
||||
); |
||||
} |
||||
// 两者都形成了镜像,但是都未指向对方,理论上不会存在该情况
|
||||
else { |
||||
errorMsg = CharSequenceUtil.format( |
||||
"attribute [{}] cannot mirror for [{}], because [{}] already mirrored for [{}] and [{}] already mirrored for [{}]", |
||||
mirror.getAttribute(), original.getAttribute(), |
||||
mirror.getAttribute(), ((MirroredAnnotationAttribute)mirror).getLinked(), |
||||
original.getAttribute(), ((MirroredAnnotationAttribute)original).getLinked() |
||||
); |
||||
} |
||||
|
||||
throw new IllegalArgumentException(errorMsg); |
||||
} |
||||
|
||||
/** |
||||
* 基本校验 |
||||
*/ |
||||
private void checkMirrorRelation(Link annotation, AnnotationAttribute original, AnnotationAttribute mirror) { |
||||
// 镜像属性必须存在
|
||||
checkLinkedAttributeNotNull(original, mirror, annotation); |
||||
// 镜像属性返回值必须一致
|
||||
checkAttributeType(original, mirror); |
||||
// 镜像属性上必须存在对应的注解
|
||||
final Link mirrorAttributeAnnotation = getLinkAnnotation(mirror, RelationType.MIRROR_FOR); |
||||
Assert.isTrue( |
||||
ObjectUtil.isNotNull(mirrorAttributeAnnotation) && RelationType.MIRROR_FOR.equals(mirrorAttributeAnnotation.type()), |
||||
"mirror attribute [{}] of original attribute [{}] must marked by @Link, and also @LinkType.type() must is [{}]", |
||||
mirror.getAttribute(), original.getAttribute(), RelationType.MIRROR_FOR |
||||
); |
||||
checkLinkedSelf(original, mirror); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,48 @@ |
||||
package cn.hutool.core.annotation; |
||||
|
||||
import cn.hutool.core.lang.Assert; |
||||
|
||||
/** |
||||
* 表示存在对应镜像属性的注解属性,当获取值时将根据{@link RelationType#MIRROR_FOR}的规则进行处理 |
||||
* |
||||
* @author huangchengxing |
||||
* @see MirrorLinkAnnotationPostProcessor |
||||
* @see RelationType#MIRROR_FOR |
||||
*/ |
||||
public class MirroredAnnotationAttribute extends AbstractWrappedAnnotationAttribute { |
||||
|
||||
public MirroredAnnotationAttribute(AnnotationAttribute origin, AnnotationAttribute linked) { |
||||
super(origin, linked); |
||||
} |
||||
|
||||
@Override |
||||
public Object getValue() { |
||||
final boolean originIsDefault = original.isValueEquivalentToDefaultValue(); |
||||
final boolean targetIsDefault = linked.isValueEquivalentToDefaultValue(); |
||||
final Object originValue = original.getValue(); |
||||
final Object targetValue = linked.getValue(); |
||||
|
||||
// 都为默认值,或都为非默认值时,两方法的返回值必须相等
|
||||
if (originIsDefault == targetIsDefault) { |
||||
Assert.equals( |
||||
originValue, targetValue, |
||||
"the values of attributes [{}] and [{}] that mirror each other are different: [{}] <==> [{}]", |
||||
original.getAttribute(), linked.getAttribute(), originValue, targetValue |
||||
); |
||||
return originValue; |
||||
} |
||||
|
||||
// 两者有一者不为默认值时,优先返回非默认值
|
||||
return originIsDefault ? targetValue : originValue; |
||||
} |
||||
|
||||
/** |
||||
* 当{@link #original}与{@link #linked}都为默认值时返回{@code true} |
||||
* |
||||
* @return 是否 |
||||
*/ |
||||
@Override |
||||
public boolean isValueEquivalentToDefaultValue() { |
||||
return original.isValueEquivalentToDefaultValue() && linked.isValueEquivalentToDefaultValue(); |
||||
} |
||||
} |
@ -0,0 +1,21 @@ |
||||
package cn.hutool.core.annotation; |
||||
|
||||
import java.lang.annotation.Documented; |
||||
import java.lang.annotation.ElementType; |
||||
import java.lang.annotation.Retention; |
||||
import java.lang.annotation.RetentionPolicy; |
||||
import java.lang.annotation.Target; |
||||
|
||||
/** |
||||
* 属性忽略注解,使用此注解的字段等会被忽略,主要用于Bean拷贝、Bean转Map等<br> |
||||
* 此注解应用于字段时,忽略读取和设置属性值,应用于setXXX方法忽略设置值,应用于getXXX忽略读取值 |
||||
* |
||||
* @author Looly |
||||
* @since 5.4.2 |
||||
*/ |
||||
@Documented |
||||
@Retention(RetentionPolicy.RUNTIME) |
||||
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER}) |
||||
public @interface PropIgnore { |
||||
|
||||
} |
@ -0,0 +1,50 @@ |
||||
package cn.hutool.core.annotation; |
||||
|
||||
/** |
||||
* <p>注解属性的关系类型 <br> |
||||
* 若将被{@link Link}注解的属性称为“原始属性”,而在{@link Link}注解中指向的注解属性称为“关联属性”, |
||||
* 则该枚举用于描述“原始属性”与“关联属性”在{@link SynthesizedAggregateAnnotation}处理过程中的作用关系。<br> |
||||
* 根据在{@link Link#type()}中指定的关系类型的不同,通过{@link SynthesizedAggregateAnnotation}合成的注解的属性值也将有所变化。 |
||||
* |
||||
* <p>当一个注解中的所有属性同时具备多种关系时,将依次按下述顺序处理: |
||||
* <ol> |
||||
* <li>属性上的{@link Alias}注解;</li> |
||||
* <li>属性上的{@link Link}注解,且{@link Link#type()}为{@link #MIRROR_FOR};</li> |
||||
* <li>属性上的{@link Link}注解,且{@link Link#type()}为{@link #FORCE_ALIAS_FOR};</li> |
||||
* <li>属性上的{@link Link}注解,且{@link Link#type()}为{@link #ALIAS_FOR};</li> |
||||
* </ol> |
||||
* |
||||
* @author huangchengxing |
||||
* @see SynthesizedAggregateAnnotation |
||||
* @see Link |
||||
*/ |
||||
public enum RelationType { |
||||
|
||||
/** |
||||
* <p>表示注解的属性与指定的属性互为镜像,通过一个属性将能够获得对方的值。<br> |
||||
* 它们遵循下述规则: |
||||
* <ul> |
||||
* <li>互为镜像的两个属性,必须同时通过指定模式为{@code MIRROR_FOR}的{@link Link}注解指定对方;</li> |
||||
* <li>互为镜像的两个属性,类型必须一致;</li> |
||||
* <li>互为镜像的两个属性在获取值,且两者的值皆不同时,必须且仅允许有一个非默认值,该值被优先返回;</li> |
||||
* <li>互为镜像的两个属性,在值都为默认值或都不为默认值时,两者的值必须相等;</li> |
||||
* </ul> |
||||
*/ |
||||
MIRROR_FOR, |
||||
|
||||
/** |
||||
* <p>表示“原始属性”将作为“关联属性”的别名。 |
||||
* <ul> |
||||
* <li>当“原始属性”为默认值时,获取“关联属性”将返回“关联属性”本身的值;</li> |
||||
* <li>当“原始属性”不为默认值时,获取“关联属性”将返回“原始属性”的值;</li> |
||||
* </ul> |
||||
*/ |
||||
ALIAS_FOR, |
||||
|
||||
/** |
||||
* <p>表示“原始属性”将强制作为“关联属性”的别名。效果等同于在“原始属性”上添加{@link Alias}注解, |
||||
* 任何情况下,获取“关联属性”的值都将直接返回“原始属性”的值 |
||||
*/ |
||||
FORCE_ALIAS_FOR |
||||
|
||||
} |
@ -0,0 +1,102 @@ |
||||
package cn.hutool.core.annotation; |
||||
|
||||
import java.lang.annotation.Annotation; |
||||
|
||||
/** |
||||
* <p>表示基于特定规则聚合,将一组注解聚合而来的注解对象, |
||||
* 该注解对象允许根据一定规则“合成”一些跟原始注解属性不一样合成注解。 |
||||
* |
||||
* <p>合成注解一般被用于处理类层级结果中具有直接或间接关联的注解对象, |
||||
* 当实例被创建时,会获取到这些注解对象,并使用{@link SynthesizedAnnotationSelector}对类型相同的注解进行过滤, |
||||
* 并最终得到类型不重复的有效注解对象。这些有效注解将被包装为{@link SynthesizedAnnotation}, |
||||
* 然后最终用于“合成”一个{@link SynthesizedAggregateAnnotation}。<br> |
||||
* {@link SynthesizedAnnotationSelector}是合成注解生命周期中的第一个钩子, |
||||
* 自定义选择器以拦截原始注解被扫描的过程。 |
||||
* |
||||
* <p>当合成注解完成对待合成注解的扫描,并完成了必要属性的加载后, |
||||
* 将会按顺序依次调用{@link SynthesizedAnnotationPostProcessor}, |
||||
* 注解后置处理器允许用于对完成注解的待合成注解进行二次调整, |
||||
* 该钩子一般用于根据{@link Link}注解对属性进行调整。<br> |
||||
* {@link SynthesizedAnnotationPostProcessor}是合成注解生命周期中的第二个钩子, |
||||
* 自定义后置处理器以拦截原始在转为待合成注解后的初始化过程。 |
||||
* |
||||
* <p>合成注解允许通过{@link #synthesize(Class)}合成一个指定的注解对象, |
||||
* 该方法返回的注解对象可能是原始的注解对象,也有可能通过动态代理的方式生成, |
||||
* 该对象实例的属性不一定来自对象本身,而是来自于经过{@link SynthesizedAnnotationAttributeProcessor} |
||||
* 处理后的、用于合成当前实例的全部关联注解的相关属性。<br> |
||||
* {@link SynthesizedAnnotationAttributeProcessor}是合成注解生命周期中的第三个钩子, |
||||
* 自定义属性处理器以拦截合成注解的取值过程。 |
||||
* |
||||
* @author huangchengxing |
||||
* @see AnnotationSynthesizer |
||||
* @see SynthesizedAnnotation |
||||
* @see SynthesizedAnnotationSelector |
||||
* @see SynthesizedAnnotationAttributeProcessor |
||||
* @see SynthesizedAnnotationPostProcessor |
||||
* @see GenericSynthesizedAggregateAnnotation |
||||
*/ |
||||
public interface SynthesizedAggregateAnnotation extends AggregateAnnotation, Hierarchical, AnnotationSynthesizer, AnnotationAttributeValueProvider { |
||||
|
||||
// ================== hierarchical ==================
|
||||
|
||||
/** |
||||
* 距离{@link #getRoot()}返回值的垂直距离, |
||||
* 默认聚合注解即为根对象,因此返回0 |
||||
* |
||||
* @return 距离{@link #getRoot()}返回值的水平距离, |
||||
*/ |
||||
@Override |
||||
default int getVerticalDistance() { |
||||
return 0; |
||||
} |
||||
|
||||
/** |
||||
* 距离{@link #getRoot()}返回值的水平距离, |
||||
* 默认聚合注解即为根对象,因此返回0 |
||||
* |
||||
* @return 距离{@link #getRoot()}返回值的水平距离, |
||||
*/ |
||||
@Override |
||||
default int getHorizontalDistance() { |
||||
return 0; |
||||
} |
||||
|
||||
// ================== synthesize ==================
|
||||
|
||||
/** |
||||
* 获取在聚合中存在的指定注解对象 |
||||
* |
||||
* @param annotationType 注解类型 |
||||
* @param <T> 注解类型 |
||||
* @return 注解对象 |
||||
*/ |
||||
<T extends Annotation> T getAnnotation(Class<T> annotationType); |
||||
|
||||
/** |
||||
* 获取合成注解属性处理器 |
||||
* |
||||
* @return 合成注解属性处理器 |
||||
*/ |
||||
SynthesizedAnnotationAttributeProcessor getAnnotationAttributeProcessor(); |
||||
|
||||
/** |
||||
* 获取当前的注解类型 |
||||
* |
||||
* @return 注解类型 |
||||
*/ |
||||
@Override |
||||
default Class<? extends Annotation> annotationType() { |
||||
return this.getClass(); |
||||
} |
||||
|
||||
/** |
||||
* 从聚合中获取指定类型的属性值 |
||||
* |
||||
* @param attributeName 属性名称 |
||||
* @param attributeType 属性类型 |
||||
* @return 属性值 |
||||
*/ |
||||
@Override |
||||
Object getAttributeValue(String attributeName, Class<?> attributeType); |
||||
|
||||
} |
@ -0,0 +1,96 @@ |
||||
package cn.hutool.core.annotation; |
||||
|
||||
import cn.hutool.core.collection.CollUtil; |
||||
|
||||
import java.lang.annotation.Annotation; |
||||
import java.util.Map; |
||||
import java.util.function.UnaryOperator; |
||||
|
||||
/** |
||||
* <p>用于在{@link SynthesizedAggregateAnnotation}中表示一个处于合成状态的注解对象。<br> |
||||
* 当对多个合成注解排序时,默认使用{@link #DEFAULT_HIERARCHICAL_COMPARATOR}进行排序, |
||||
* 从保证合成注解按{@link #getVerticalDistance()}与{@link #getHorizontalDistance()}的返回值保持有序, |
||||
* 从而使得距离根元素更接近的注解对象在被处理是具有更高的优先级。 |
||||
* |
||||
* @author huangchengxing |
||||
* @see SynthesizedAggregateAnnotation |
||||
*/ |
||||
public interface SynthesizedAnnotation extends Annotation, Hierarchical, AnnotationAttributeValueProvider { |
||||
|
||||
/** |
||||
* 获取被合成的注解对象 |
||||
* |
||||
* @return 注解对象 |
||||
*/ |
||||
Annotation getAnnotation(); |
||||
|
||||
/** |
||||
* 获取该合成注解与根对象的垂直距离。 |
||||
* 默认情况下,该距离即为当前注解与根对象之间相隔的层级数。 |
||||
* |
||||
* @return 合成注解与根对象的垂直距离 |
||||
*/ |
||||
@Override |
||||
int getVerticalDistance(); |
||||
|
||||
/** |
||||
* 获取该合成注解与根对象的水平距离。 |
||||
* 默认情况下,该距离即为当前注解与根对象之间相隔的已经被扫描到的注解数。 |
||||
* |
||||
* @return 合成注解与根对象的水平距离 |
||||
*/ |
||||
@Override |
||||
int getHorizontalDistance(); |
||||
|
||||
/** |
||||
* 注解是否存在该名称相同,且类型一致的属性 |
||||
* |
||||
* @param attributeName 属性名 |
||||
* @param returnType 返回值类型 |
||||
* @return 是否存在该属性 |
||||
*/ |
||||
boolean hasAttribute(String attributeName, Class<?> returnType); |
||||
|
||||
/** |
||||
* 获取该注解的全部属性 |
||||
* |
||||
* @return 注解属性 |
||||
*/ |
||||
Map<String, AnnotationAttribute> getAttributes(); |
||||
|
||||
/** |
||||
* 设置该注解的全部属性 |
||||
* |
||||
* @param attributes 注解属性 |
||||
*/ |
||||
default void setAttributes(Map<String, AnnotationAttribute> attributes) { |
||||
if (CollUtil.isNotEmpty(attributes)) { |
||||
attributes.forEach(this::setAttribute); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 设置属性值 |
||||
* |
||||
* @param attributeName 属性名称 |
||||
* @param attribute 注解属性 |
||||
*/ |
||||
void setAttribute(String attributeName, AnnotationAttribute attribute); |
||||
|
||||
/** |
||||
* 替换属性值 |
||||
* |
||||
* @param attributeName 属性名 |
||||
* @param operator 替换操作 |
||||
*/ |
||||
void replaceAttribute(String attributeName, UnaryOperator<AnnotationAttribute> operator); |
||||
|
||||
/** |
||||
* 获取属性值 |
||||
* |
||||
* @param attributeName 属性名 |
||||
* @return 属性值 |
||||
*/ |
||||
Object getAttributeValue(String attributeName); |
||||
|
||||
} |
@ -0,0 +1,24 @@ |
||||
package cn.hutool.core.annotation; |
||||
|
||||
import java.util.Collection; |
||||
|
||||
/** |
||||
* 合成注解属性选择器。用于在{@link SynthesizedAggregateAnnotation}中从指定类型的合成注解里获取到对应的属性值 |
||||
* |
||||
* @author huangchengxing |
||||
*/ |
||||
@FunctionalInterface |
||||
public interface SynthesizedAnnotationAttributeProcessor { |
||||
|
||||
/** |
||||
* 从一批被合成注解中,获取指定名称与类型的属性值 |
||||
* |
||||
* @param attributeName 属性名称 |
||||
* @param attributeType 属性类型 |
||||
* @param synthesizedAnnotations 被合成的注解 |
||||
* @param <R> 属性类型 |
||||
* @return 属性值 |
||||
*/ |
||||
<R> R getAttributeValue(String attributeName, Class<R> attributeType, Collection<? extends SynthesizedAnnotation> synthesizedAnnotations); |
||||
|
||||
} |
@ -0,0 +1,71 @@ |
||||
package cn.hutool.core.annotation; |
||||
|
||||
import cn.hutool.core.comparator.CompareUtil; |
||||
|
||||
import java.util.Comparator; |
||||
|
||||
/** |
||||
* <p>被合成注解后置处理器,用于在{@link SynthesizedAggregateAnnotation}加载完所有待合成注解后, |
||||
* 再对加载好的{@link SynthesizedAnnotation}进行后置处理。<br> |
||||
* 当多个{@link SynthesizedAnnotationPostProcessor}需要一起执行时,将按照{@link #order()}的返回值进行排序, |
||||
* 该值更小的处理器将被优先执行。 |
||||
* |
||||
* <p>该接口存在多个实现类,调用者应当保证在任何时候,对一批后置处理器的调用顺序都符合: |
||||
* <ul> |
||||
* <li>{@link AliasAnnotationPostProcessor};</li> |
||||
* <li>{@link MirrorLinkAnnotationPostProcessor};</li> |
||||
* <li>{@link AliasLinkAnnotationPostProcessor};</li> |
||||
* <li>其他后置处理器;</li> |
||||
* </ul> |
||||
* |
||||
* @author huangchengxing |
||||
* @see AliasAnnotationPostProcessor |
||||
* @see MirrorLinkAnnotationPostProcessor |
||||
* @see AliasLinkAnnotationPostProcessor |
||||
*/ |
||||
public interface SynthesizedAnnotationPostProcessor extends Comparable<SynthesizedAnnotationPostProcessor> { |
||||
|
||||
/** |
||||
* 属性上带有{@link Alias}的注解对象的后置处理器 |
||||
*/ |
||||
AliasAnnotationPostProcessor ALIAS_ANNOTATION_POST_PROCESSOR = new AliasAnnotationPostProcessor(); |
||||
|
||||
/** |
||||
* 属性上带有{@link Link},且与其他注解的属性存在镜像关系的注解对象的后置处理器 |
||||
*/ |
||||
MirrorLinkAnnotationPostProcessor MIRROR_LINK_ANNOTATION_POST_PROCESSOR = new MirrorLinkAnnotationPostProcessor(); |
||||
|
||||
/** |
||||
* 属性上带有{@link Link},且与其他注解的属性存在别名关系的注解对象的后置处理器 |
||||
*/ |
||||
AliasLinkAnnotationPostProcessor ALIAS_LINK_ANNOTATION_POST_PROCESSOR = new AliasLinkAnnotationPostProcessor(); |
||||
|
||||
/** |
||||
* 在一组后置处理器中被调用的顺序,越小越靠前 |
||||
* |
||||
* @return 排序值 |
||||
*/ |
||||
default int order() { |
||||
return Integer.MAX_VALUE; |
||||
} |
||||
|
||||
/** |
||||
* 比较两个后置处理器的{@link #order()}返回值 |
||||
* |
||||
* @param o 比较对象 |
||||
* @return 大小 |
||||
*/ |
||||
@Override |
||||
default int compareTo(SynthesizedAnnotationPostProcessor o) { |
||||
return CompareUtil.compare(this, o, Comparator.comparing(SynthesizedAnnotationPostProcessor::order)); |
||||
} |
||||
|
||||
/** |
||||
* 给定指定被合成注解与其所属的合成注解聚合器实例,经过处理后返回最终 |
||||
* |
||||
* @param synthesizedAnnotation 合成的注解 |
||||
* @param synthesizer 注解合成器 |
||||
*/ |
||||
void process(SynthesizedAnnotation synthesizedAnnotation, AnnotationSynthesizer synthesizer); |
||||
|
||||
} |
@ -0,0 +1,160 @@ |
||||
package cn.hutool.core.annotation; |
||||
|
||||
import cn.hutool.core.lang.Assert; |
||||
import cn.hutool.core.lang.Opt; |
||||
import cn.hutool.core.text.CharSequenceUtil; |
||||
import cn.hutool.core.util.ClassUtil; |
||||
import cn.hutool.core.util.ObjectUtil; |
||||
import cn.hutool.core.util.ReflectUtil; |
||||
|
||||
import java.lang.annotation.Annotation; |
||||
import java.lang.reflect.InvocationHandler; |
||||
import java.lang.reflect.Method; |
||||
import java.lang.reflect.Proxy; |
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
import java.util.Objects; |
||||
import java.util.function.BiFunction; |
||||
import java.util.stream.Collectors; |
||||
import java.util.stream.Stream; |
||||
|
||||
/** |
||||
* 合成注解代理类,用于为{@link SynthesizedAnnotation}生成对应的合成注解代理对象 |
||||
* |
||||
* @author huangchengxing |
||||
* @see SynthesizedAnnotation |
||||
* @see AnnotationAttributeValueProvider |
||||
*/ |
||||
public class SynthesizedAnnotationProxy implements InvocationHandler { |
||||
|
||||
private final AnnotationAttributeValueProvider annotationAttributeValueProvider; |
||||
private final SynthesizedAnnotation annotation; |
||||
private final Map<String, BiFunction<Method, Object[], Object>> methods; |
||||
|
||||
/** |
||||
* 创建一个代理注解,生成的代理对象将是{@link SyntheticProxyAnnotation}与指定的注解类的子类。 |
||||
* |
||||
* @param <T> 注解类型 |
||||
* @param annotationType 注解类型 |
||||
* @param annotationAttributeValueProvider 注解属性值获取器 |
||||
* @param annotation 合成注解 |
||||
* @return 代理注解 |
||||
*/ |
||||
@SuppressWarnings("unchecked") |
||||
public static <T extends Annotation> T create( |
||||
Class<T> annotationType, |
||||
AnnotationAttributeValueProvider annotationAttributeValueProvider, |
||||
SynthesizedAnnotation annotation) { |
||||
if (ObjectUtil.isNull(annotation)) { |
||||
return null; |
||||
} |
||||
final SynthesizedAnnotationProxy proxyHandler = new SynthesizedAnnotationProxy(annotationAttributeValueProvider, annotation); |
||||
if (ObjectUtil.isNull(annotation)) { |
||||
return null; |
||||
} |
||||
return (T) Proxy.newProxyInstance( |
||||
annotationType.getClassLoader(), |
||||
new Class[]{annotationType, SyntheticProxyAnnotation.class}, |
||||
proxyHandler |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* 创建一个代理注解,生成的代理对象将是{@link SyntheticProxyAnnotation}与指定的注解类的子类。 |
||||
* |
||||
* @param <T> 注解类型 |
||||
* @param annotationType 注解类型 |
||||
* @param annotation 合成注解 |
||||
* @return 代理注解 |
||||
*/ |
||||
public static <T extends Annotation> T create( |
||||
Class<T> annotationType, SynthesizedAnnotation annotation) { |
||||
return create(annotationType, annotation, annotation); |
||||
} |
||||
|
||||
/** |
||||
* 该类是否为通过{@code SynthesizedAnnotationProxy}生成的代理类 |
||||
* |
||||
* @param annotationType 注解类型 |
||||
* @return 是否 |
||||
*/ |
||||
public static boolean isProxyAnnotation(Class<?> annotationType) { |
||||
return ClassUtil.isAssignable(SyntheticProxyAnnotation.class, annotationType); |
||||
} |
||||
|
||||
SynthesizedAnnotationProxy(AnnotationAttributeValueProvider annotationAttributeValueProvider, SynthesizedAnnotation annotation) { |
||||
Assert.notNull(annotationAttributeValueProvider, "annotationAttributeValueProvider must not null"); |
||||
Assert.notNull(annotation, "annotation must not null"); |
||||
this.annotationAttributeValueProvider = annotationAttributeValueProvider; |
||||
this.annotation = annotation; |
||||
this.methods = new HashMap<>(9); |
||||
loadMethods(); |
||||
} |
||||
|
||||
@Override |
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { |
||||
return Opt.ofNullable(methods.get(method.getName())) |
||||
.map(m -> m.apply(method, args)) |
||||
.orElseGet(() -> ReflectUtil.invoke(this, method, args)); |
||||
} |
||||
|
||||
// ========================= 代理方法 =========================
|
||||
|
||||
void loadMethods() { |
||||
methods.put("toString", (method, args) -> proxyToString()); |
||||
methods.put("hashCode", (method, args) -> proxyHashCode()); |
||||
methods.put("getSynthesizedAnnotation", (method, args) -> proxyGetSynthesizedAnnotation()); |
||||
methods.put("getRoot", (method, args) -> annotation.getRoot()); |
||||
methods.put("getVerticalDistance", (method, args) -> annotation.getVerticalDistance()); |
||||
methods.put("getHorizontalDistance", (method, args) -> annotation.getHorizontalDistance()); |
||||
methods.put("hasAttribute", (method, args) -> annotation.hasAttribute((String) args[0], (Class<?>) args[1])); |
||||
methods.put("getAttributes", (method, args) -> annotation.getAttributes()); |
||||
methods.put("setAttribute", (method, args) -> { |
||||
throw new UnsupportedOperationException("proxied annotation can not reset attributes"); |
||||
}); |
||||
methods.put("getAttributeValue", (method, args) -> annotation.getAttributeValue((String) args[0])); |
||||
methods.put("annotationType", (method, args) -> annotation.annotationType()); |
||||
for (final Method declaredMethod : ClassUtil.getDeclaredMethods(annotation.getAnnotation().annotationType())) { |
||||
methods.put(declaredMethod.getName(), (method, args) -> proxyAttributeValue(method)); |
||||
} |
||||
} |
||||
|
||||
private String proxyToString() { |
||||
final String attributes = Stream.of(ClassUtil.getDeclaredMethods(annotation.getAnnotation().annotationType())) |
||||
.filter(AnnotationUtil::isAttributeMethod) |
||||
.map(method -> CharSequenceUtil.format( |
||||
"{}={}", method.getName(), proxyAttributeValue(method)) |
||||
) |
||||
.collect(Collectors.joining(", ")); |
||||
return CharSequenceUtil.format("@{}({})", annotation.annotationType().getName(), attributes); |
||||
} |
||||
|
||||
private int proxyHashCode() { |
||||
return Objects.hash(annotationAttributeValueProvider, annotation); |
||||
} |
||||
|
||||
private Object proxyGetSynthesizedAnnotation() { |
||||
return annotation; |
||||
} |
||||
|
||||
private Object proxyAttributeValue(Method attributeMethod) { |
||||
return annotationAttributeValueProvider.getAttributeValue(attributeMethod.getName(), attributeMethod.getReturnType()); |
||||
} |
||||
|
||||
/** |
||||
* 通过代理类生成的合成注解 |
||||
* |
||||
* @author huangchengxing |
||||
*/ |
||||
interface SyntheticProxyAnnotation extends SynthesizedAnnotation { |
||||
|
||||
/** |
||||
* 获取该代理注解对应的已合成注解 |
||||
* |
||||
* @return 理注解对应的已合成注解 |
||||
*/ |
||||
SynthesizedAnnotation getSynthesizedAnnotation(); |
||||
|
||||
} |
||||
|
||||
} |
@ -0,0 +1,82 @@ |
||||
package cn.hutool.core.annotation; |
||||
|
||||
/** |
||||
* 注解选择器,指定两个注解,选择其中一个返回。<br> |
||||
* 该接口用于在{@link SynthesizedAggregateAnnotation}中用于从一批相同的注解对象中筛选最终用于合成注解对象。 |
||||
* |
||||
* @author huangchengxing |
||||
*/ |
||||
@FunctionalInterface |
||||
public interface SynthesizedAnnotationSelector { |
||||
|
||||
/** |
||||
* 返回距离根对象更近的注解,当距离一样时优先返回旧注解 |
||||
*/ |
||||
SynthesizedAnnotationSelector NEAREST_AND_OLDEST_PRIORITY = new NearestAndOldestPrioritySelector(); |
||||
|
||||
/** |
||||
* 返回距离根对象更近的注解,当距离一样时优先返回新注解 |
||||
*/ |
||||
SynthesizedAnnotationSelector NEAREST_AND_NEWEST_PRIORITY = new NearestAndNewestPrioritySelector(); |
||||
|
||||
/** |
||||
* 返回距离根对象更远的注解,当距离一样时优先返回旧注解 |
||||
*/ |
||||
SynthesizedAnnotationSelector FARTHEST_AND_OLDEST_PRIORITY = new FarthestAndOldestPrioritySelector(); |
||||
|
||||
/** |
||||
* 返回距离根对象更远的注解,当距离一样时优先返回新注解 |
||||
*/ |
||||
SynthesizedAnnotationSelector FARTHEST_AND_NEWEST_PRIORITY = new FarthestAndNewestPrioritySelector(); |
||||
|
||||
/** |
||||
* 比较两个被合成的注解,选择其中的一个并返回 |
||||
* |
||||
* @param <T> 复合注解类型 |
||||
* @param oldAnnotation 已存在的注解,该参数不允许为空 |
||||
* @param newAnnotation 新获取的注解,该参数不允许为空 |
||||
* @return 被合成的注解 |
||||
*/ |
||||
<T extends SynthesizedAnnotation> T choose(T oldAnnotation, T newAnnotation); |
||||
|
||||
/** |
||||
* 返回距离根对象更近的注解,当距离一样时优先返回旧注解 |
||||
*/ |
||||
class NearestAndOldestPrioritySelector implements SynthesizedAnnotationSelector { |
||||
@Override |
||||
public <T extends SynthesizedAnnotation> T choose(T oldAnnotation, T newAnnotation) { |
||||
return Hierarchical.Selector.NEAREST_AND_OLDEST_PRIORITY.choose(oldAnnotation, newAnnotation); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 返回距离根对象更近的注解,当距离一样时优先返回新注解 |
||||
*/ |
||||
class NearestAndNewestPrioritySelector implements SynthesizedAnnotationSelector { |
||||
@Override |
||||
public <T extends SynthesizedAnnotation> T choose(T oldAnnotation, T newAnnotation) { |
||||
return Hierarchical.Selector.NEAREST_AND_NEWEST_PRIORITY.choose(oldAnnotation, newAnnotation); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 返回距离根对象更远的注解,当距离一样时优先返回旧注解 |
||||
*/ |
||||
class FarthestAndOldestPrioritySelector implements SynthesizedAnnotationSelector { |
||||
@Override |
||||
public <T extends SynthesizedAnnotation> T choose(T oldAnnotation, T newAnnotation) { |
||||
return Hierarchical.Selector.FARTHEST_AND_OLDEST_PRIORITY.choose(oldAnnotation, newAnnotation); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 返回距离根对象更远的注解,当距离一样时优先返回新注解 |
||||
*/ |
||||
class FarthestAndNewestPrioritySelector implements SynthesizedAnnotationSelector { |
||||
@Override |
||||
public <T extends SynthesizedAnnotation> T choose(T oldAnnotation, T newAnnotation) { |
||||
return Hierarchical.Selector.FARTHEST_AND_NEWEST_PRIORITY.choose(oldAnnotation, newAnnotation); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,125 @@ |
||||
package cn.hutool.core.annotation; |
||||
|
||||
import java.lang.annotation.Annotation; |
||||
import java.lang.reflect.Method; |
||||
import java.util.Collection; |
||||
|
||||
/** |
||||
* <p>表示一个被包装过的{@link AnnotationAttribute}, |
||||
* 该实例中的一些方法可能会被代理到另一个注解属性对象中, |
||||
* 从而使得通过原始的注解属性的方法获取到另一注解属性的值。<br> |
||||
* 除了{@link #getValue()}以外,其他方法的返回值应当尽可能与{@link #getOriginal()} |
||||
* 返回的{@link AnnotationAttribute}对象的方法返回值一致。 |
||||
* |
||||
* <p>当包装类被包装了多层后,则规则生效优先级按包装的先后顺序倒序排序, |
||||
* 比如a、b互为镜像,此时a、b两属性应当都被{@link MirroredAnnotationAttribute}包装, |
||||
* 若再指定c为a的别名字段,则c、a、b都要在原基础上再次包装一层{@link AliasedAnnotationAttribute}。<br> |
||||
* 此时a、b同时被包装了两层,则执行时,优先执行{@link AliasedAnnotationAttribute}的逻辑, |
||||
* 当该规则不生效时,比如c只有默认值,此时上一次的{@link MirroredAnnotationAttribute}的逻辑才会生效。 |
||||
* |
||||
* <p>被包装的{@link AnnotationAttribute}实际结构为一颗二叉树, |
||||
* 当包装类再次被包装时,实际上等于又添加了一个新的根节点, |
||||
* 此时需要同时更新树的全部关联叶子节点。 |
||||
* |
||||
* @author huangchengxing |
||||
* @see AnnotationAttribute |
||||
* @see ForceAliasedAnnotationAttribute |
||||
* @see AliasedAnnotationAttribute |
||||
* @see MirroredAnnotationAttribute |
||||
*/ |
||||
public interface WrappedAnnotationAttribute extends AnnotationAttribute { |
||||
|
||||
// =========================== 新增方法 ===========================
|
||||
|
||||
/** |
||||
* 获取被包装的{@link AnnotationAttribute}对象,该对象也可能是{@link AnnotationAttribute} |
||||
* |
||||
* @return 被包装的{@link AnnotationAttribute}对象 |
||||
*/ |
||||
AnnotationAttribute getOriginal(); |
||||
|
||||
/** |
||||
* 获取最初的被包装的{@link AnnotationAttribute} |
||||
* |
||||
* @return 最初的被包装的{@link AnnotationAttribute} |
||||
*/ |
||||
AnnotationAttribute getNonWrappedOriginal(); |
||||
|
||||
/** |
||||
* 获取包装{@link #getOriginal()}的{@link AnnotationAttribute}对象,该对象也可能是{@link AnnotationAttribute} |
||||
* |
||||
* @return 包装对象 |
||||
*/ |
||||
AnnotationAttribute getLinked(); |
||||
|
||||
/** |
||||
* 遍历以当前实例为根节点的树结构,获取所有未被包装的属性 |
||||
* |
||||
* @return 叶子节点 |
||||
*/ |
||||
Collection<AnnotationAttribute> getAllLinkedNonWrappedAttributes(); |
||||
|
||||
// =========================== 代理实现 ===========================
|
||||
|
||||
/** |
||||
* 获取注解对象 |
||||
* |
||||
* @return 注解对象 |
||||
*/ |
||||
@Override |
||||
default Annotation getAnnotation() { |
||||
return getOriginal().getAnnotation(); |
||||
} |
||||
|
||||
/** |
||||
* 获取注解属性对应的方法 |
||||
* |
||||
* @return 注解属性对应的方法 |
||||
*/ |
||||
@Override |
||||
default Method getAttribute() { |
||||
return getOriginal().getAttribute(); |
||||
} |
||||
|
||||
/** |
||||
* 该注解属性的值是否等于默认值 <br> |
||||
* 默认仅当{@link #getOriginal()}与{@link #getLinked()}返回的注解属性 |
||||
* 都为默认值时,才返回{@code true} |
||||
* |
||||
* @return 该注解属性的值是否等于默认值 |
||||
*/ |
||||
@Override |
||||
boolean isValueEquivalentToDefaultValue(); |
||||
|
||||
/** |
||||
* 获取属性类型 |
||||
* |
||||
* @return 属性类型 |
||||
*/ |
||||
@Override |
||||
default Class<?> getAttributeType() { |
||||
return getOriginal().getAttributeType(); |
||||
} |
||||
|
||||
/** |
||||
* 获取属性上的注解 |
||||
* |
||||
* @param annotationType 注解类型 |
||||
* @return 注解对象 |
||||
*/ |
||||
@Override |
||||
default <T extends Annotation> T getAnnotation(Class<T> annotationType) { |
||||
return getOriginal().getAnnotation(annotationType); |
||||
} |
||||
|
||||
/** |
||||
* 当前注解属性是否已经被{@link WrappedAnnotationAttribute}包装 |
||||
* |
||||
* @return boolean |
||||
*/ |
||||
@Override |
||||
default boolean isWrapped() { |
||||
return true; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,7 @@ |
||||
/** |
||||
* 注解包,提供增强型注解和注解工具类 |
||||
* |
||||
* @author looly |
||||
* |
||||
*/ |
||||
package cn.hutool.core.annotation; |
@ -0,0 +1,288 @@ |
||||
package cn.hutool.core.annotation.scanner; |
||||
|
||||
import cn.hutool.core.annotation.AnnotationUtil; |
||||
import cn.hutool.core.collection.CollUtil; |
||||
import cn.hutool.core.lang.Assert; |
||||
import cn.hutool.core.util.ArrayUtil; |
||||
import cn.hutool.core.util.ObjectUtil; |
||||
|
||||
import java.lang.annotation.Annotation; |
||||
import java.lang.reflect.AnnotatedElement; |
||||
import java.lang.reflect.Proxy; |
||||
import java.util.*; |
||||
import java.util.function.BiConsumer; |
||||
import java.util.function.Predicate; |
||||
import java.util.function.UnaryOperator; |
||||
|
||||
/** |
||||
* 为需要从类的层级结构中获取注解的{@link AnnotationScanner}提供基本实现 |
||||
* |
||||
* @author huangchengxing |
||||
*/ |
||||
public abstract class AbstractTypeAnnotationScanner<T extends AbstractTypeAnnotationScanner<T>> implements AnnotationScanner { |
||||
|
||||
/** |
||||
* 是否允许扫描父类 |
||||
*/ |
||||
private boolean includeSuperClass; |
||||
|
||||
/** |
||||
* 是否允许扫描父接口 |
||||
*/ |
||||
private boolean includeInterfaces; |
||||
|
||||
/** |
||||
* 过滤器,若类型无法通过该过滤器,则该类型及其树结构将直接不被查找 |
||||
*/ |
||||
private Predicate<Class<?>> filter; |
||||
|
||||
/** |
||||
* 排除的类型,以上类型及其树结构将直接不被查找 |
||||
*/ |
||||
private final Set<Class<?>> excludeTypes; |
||||
|
||||
/** |
||||
* 转换器 |
||||
*/ |
||||
private final List<UnaryOperator<Class<?>>> converters; |
||||
|
||||
/** |
||||
* 是否有转换器 |
||||
*/ |
||||
private boolean hasConverters; |
||||
|
||||
/** |
||||
* 当前实例 |
||||
*/ |
||||
private final T typedThis; |
||||
|
||||
/** |
||||
* 构造一个类注解扫描器 |
||||
* |
||||
* @param includeSuperClass 是否允许扫描父类 |
||||
* @param includeInterfaces 是否允许扫描父接口 |
||||
* @param filter 过滤器 |
||||
* @param excludeTypes 不包含的类型 |
||||
*/ |
||||
@SuppressWarnings("unchecked") |
||||
protected AbstractTypeAnnotationScanner(boolean includeSuperClass, boolean includeInterfaces, Predicate<Class<?>> filter, Set<Class<?>> excludeTypes) { |
||||
Assert.notNull(filter, "filter must not null"); |
||||
Assert.notNull(excludeTypes, "excludeTypes must not null"); |
||||
this.includeSuperClass = includeSuperClass; |
||||
this.includeInterfaces = includeInterfaces; |
||||
this.filter = filter; |
||||
this.excludeTypes = excludeTypes; |
||||
this.converters = new ArrayList<>(); |
||||
this.typedThis = (T) this; |
||||
} |
||||
|
||||
/** |
||||
* 是否允许扫描父类 |
||||
* |
||||
* @return 是否允许扫描父类 |
||||
*/ |
||||
public boolean isIncludeSuperClass() { |
||||
return includeSuperClass; |
||||
} |
||||
|
||||
/** |
||||
* 是否允许扫描父接口 |
||||
* |
||||
* @return 是否允许扫描父接口 |
||||
*/ |
||||
public boolean isIncludeInterfaces() { |
||||
return includeInterfaces; |
||||
} |
||||
|
||||
/** |
||||
* 设置过滤器,若类型无法通过该过滤器,则该类型及其树结构将直接不被查找 |
||||
* |
||||
* @param filter 过滤器 |
||||
* @return 当前实例 |
||||
*/ |
||||
public T setFilter(Predicate<Class<?>> filter) { |
||||
Assert.notNull(filter, "filter must not null"); |
||||
this.filter = filter; |
||||
return typedThis; |
||||
} |
||||
|
||||
/** |
||||
* 添加不扫描的类型,该类型及其树结构将直接不被查找 |
||||
* |
||||
* @param excludeTypes 不扫描的类型 |
||||
* @return 当前实例 |
||||
*/ |
||||
public T addExcludeTypes(Class<?>... excludeTypes) { |
||||
CollUtil.addAll(this.excludeTypes, excludeTypes); |
||||
return typedThis; |
||||
} |
||||
|
||||
/** |
||||
* 添加转换器 |
||||
* |
||||
* @param converter 转换器 |
||||
* @return 当前实例 |
||||
* @see JdkProxyClassConverter |
||||
*/ |
||||
public T addConverters(UnaryOperator<Class<?>> converter) { |
||||
Assert.notNull(converter, "converter must not null"); |
||||
this.converters.add(converter); |
||||
if (!this.hasConverters) { |
||||
this.hasConverters = CollUtil.isNotEmpty(this.converters); |
||||
} |
||||
return typedThis; |
||||
} |
||||
|
||||
/** |
||||
* 是否允许扫描父类 |
||||
* |
||||
* @param includeSuperClass 是否 |
||||
* @return 当前实例 |
||||
*/ |
||||
protected T setIncludeSuperClass(boolean includeSuperClass) { |
||||
this.includeSuperClass = includeSuperClass; |
||||
return typedThis; |
||||
} |
||||
|
||||
/** |
||||
* 是否允许扫描父接口 |
||||
* |
||||
* @param includeInterfaces 是否 |
||||
* @return 当前实例 |
||||
*/ |
||||
protected T setIncludeInterfaces(boolean includeInterfaces) { |
||||
this.includeInterfaces = includeInterfaces; |
||||
return typedThis; |
||||
} |
||||
|
||||
/** |
||||
* 则根据广度优先递归扫描类的层级结构,并对层级结构中类/接口声明的层级索引和它们声明的注解对象进行处理 |
||||
* |
||||
* @param consumer 对获取到的注解和注解对应的层级索引的处理 |
||||
* @param annotatedEle 注解元素 |
||||
* @param filter 注解过滤器,无法通过过滤器的注解不会被处理。该参数允许为空。 |
||||
*/ |
||||
@Override |
||||
public void scan(BiConsumer<Integer, Annotation> consumer, AnnotatedElement annotatedEle, Predicate<Annotation> filter) { |
||||
filter = ObjectUtil.defaultIfNull(filter, a -> annotation -> true); |
||||
final Class<?> sourceClass = getClassFormAnnotatedElement(annotatedEle); |
||||
final Deque<List<Class<?>>> classDeque = CollUtil.newLinkedList(CollUtil.newArrayList(sourceClass)); |
||||
final Set<Class<?>> accessedTypes = new LinkedHashSet<>(); |
||||
int index = 0; |
||||
while (!classDeque.isEmpty()) { |
||||
final List<Class<?>> currClassQueue = classDeque.removeFirst(); |
||||
final List<Class<?>> nextClassQueue = new ArrayList<>(); |
||||
for (Class<?> targetClass : currClassQueue) { |
||||
targetClass = convert(targetClass); |
||||
// 过滤不需要处理的类
|
||||
if (isNotNeedProcess(accessedTypes, targetClass)) { |
||||
continue; |
||||
} |
||||
accessedTypes.add(targetClass); |
||||
// 扫描父类
|
||||
scanSuperClassIfNecessary(nextClassQueue, targetClass); |
||||
// 扫描接口
|
||||
scanInterfaceIfNecessary(nextClassQueue, targetClass); |
||||
// 处理层级索引和注解
|
||||
final Annotation[] targetAnnotations = getAnnotationsFromTargetClass(annotatedEle, index, targetClass); |
||||
for (final Annotation annotation : targetAnnotations) { |
||||
if (AnnotationUtil.isNotJdkMateAnnotation(annotation.annotationType()) && filter.test(annotation)) { |
||||
consumer.accept(index, annotation); |
||||
} |
||||
} |
||||
index++; |
||||
} |
||||
if (CollUtil.isNotEmpty(nextClassQueue)) { |
||||
classDeque.addLast(nextClassQueue); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 从要搜索的注解元素上获得要递归的类型 |
||||
* |
||||
* @param annotatedElement 注解元素 |
||||
* @return 要递归的类型 |
||||
*/ |
||||
protected abstract Class<?> getClassFormAnnotatedElement(AnnotatedElement annotatedElement); |
||||
|
||||
/** |
||||
* 从类上获取最终所需的目标注解 |
||||
* |
||||
* @param source 最初的注解元素 |
||||
* @param index 类的层级索引 |
||||
* @param targetClass 类 |
||||
* @return 最终所需的目标注解 |
||||
*/ |
||||
protected abstract Annotation[] getAnnotationsFromTargetClass(AnnotatedElement source, int index, Class<?> targetClass); |
||||
|
||||
/** |
||||
* 当前类是否不需要处理 |
||||
* |
||||
* @param accessedTypes 访问类型 |
||||
* @param targetClass 目标类型 |
||||
* @return 是否不需要处理 |
||||
*/ |
||||
protected boolean isNotNeedProcess(Set<Class<?>> accessedTypes, Class<?> targetClass) { |
||||
return ObjectUtil.isNull(targetClass) |
||||
|| accessedTypes.contains(targetClass) |
||||
|| excludeTypes.contains(targetClass) |
||||
|| filter.negate().test(targetClass); |
||||
} |
||||
|
||||
/** |
||||
* 若{@link #includeInterfaces}为{@code true},则将目标类的父接口也添加到nextClasses |
||||
* |
||||
* @param nextClasses 下一个类集合 |
||||
* @param targetClass 目标类型 |
||||
*/ |
||||
protected void scanInterfaceIfNecessary(List<Class<?>> nextClasses, Class<?> targetClass) { |
||||
if (includeInterfaces) { |
||||
final Class<?>[] interfaces = targetClass.getInterfaces(); |
||||
if (ArrayUtil.isNotEmpty(interfaces)) { |
||||
CollUtil.addAll(nextClasses, interfaces); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 若{@link #includeSuperClass}为{@code true},则将目标类的父类也添加到nextClasses |
||||
* |
||||
* @param nextClassQueue 下一个类队列 |
||||
* @param targetClass 目标类型 |
||||
*/ |
||||
protected void scanSuperClassIfNecessary(List<Class<?>> nextClassQueue, Class<?> targetClass) { |
||||
if (includeSuperClass) { |
||||
final Class<?> superClass = targetClass.getSuperclass(); |
||||
if (!ObjectUtil.equals(superClass, Object.class) && ObjectUtil.isNotNull(superClass)) { |
||||
nextClassQueue.add(superClass); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 若存在转换器,则使用转换器对目标类进行转换 |
||||
* |
||||
* @param target 目标类 |
||||
* @return 转换后的类 |
||||
*/ |
||||
protected Class<?> convert(Class<?> target) { |
||||
if (hasConverters) { |
||||
for (final UnaryOperator<Class<?>> converter : converters) { |
||||
target = converter.apply(target); |
||||
} |
||||
} |
||||
return target; |
||||
} |
||||
|
||||
/** |
||||
* 若类型为jdk代理类,则尝试转换为原始被代理类 |
||||
*/ |
||||
public static class JdkProxyClassConverter implements UnaryOperator<Class<?>> { |
||||
@Override |
||||
public Class<?> apply(Class<?> sourceClass) { |
||||
return Proxy.isProxyClass(sourceClass) ? apply(sourceClass.getSuperclass()) : sourceClass; |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,198 @@ |
||||
package cn.hutool.core.annotation.scanner; |
||||
|
||||
import cn.hutool.core.annotation.AnnotationUtil; |
||||
import cn.hutool.core.util.ArrayUtil; |
||||
import cn.hutool.core.util.ObjectUtil; |
||||
|
||||
import java.lang.annotation.Annotation; |
||||
import java.lang.annotation.Inherited; |
||||
import java.lang.reflect.AnnotatedElement; |
||||
import java.util.ArrayList; |
||||
import java.util.Collection; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
import java.util.function.BiConsumer; |
||||
import java.util.function.Predicate; |
||||
import java.util.stream.Collectors; |
||||
import java.util.stream.Stream; |
||||
|
||||
/** |
||||
* <p>注解扫描器,用于从支持的可注解元素上获取所需注解 |
||||
* |
||||
* <p>默认提供了以下扫描方式: |
||||
* <ul> |
||||
* <li>{@link #NOTHING}:什么都不做,什么注解都不扫描;</li> |
||||
* <li>{@link #DIRECTLY}:扫描元素本身直接声明的注解,包括父类带有{@link Inherited}、被传递到元素上的注解;</li> |
||||
* <li> |
||||
* {@link #DIRECTLY_AND_META_ANNOTATION}:扫描元素本身直接声明的注解,包括父类带有{@link Inherited}、被传递到元素上的注解, |
||||
* 以及这些注解的元注解; |
||||
* </li> |
||||
* <li>{@link #SUPERCLASS}:扫描元素本身以及父类的层级结构中声明的注解;</li> |
||||
* <li>{@link #SUPERCLASS_AND_META_ANNOTATION}:扫描元素本身以及父类的层级结构中声明的注解,以及这些注解的元注解;</li> |
||||
* <li>{@link #INTERFACE}:扫描元素本身以及父接口的层级结构中声明的注解;</li> |
||||
* <li>{@link #INTERFACE_AND_META_ANNOTATION}:扫描元素本身以及父接口的层级结构中声明的注解,以及这些注解的元注解;</li> |
||||
* <li>{@link #TYPE_HIERARCHY}:扫描元素本身以及父类、父接口的层级结构中声明的注解;</li> |
||||
* <li>{@link #TYPE_HIERARCHY_AND_META_ANNOTATION}:扫描元素本身以及父接口、父接口的层级结构中声明的注解,以及这些注解的元注解;</li> |
||||
* </ul> |
||||
* |
||||
* @author huangchengxing |
||||
* @see TypeAnnotationScanner |
||||
* @see MethodAnnotationScanner |
||||
* @see FieldAnnotationScanner |
||||
* @see MetaAnnotationScanner |
||||
* @see ElementAnnotationScanner |
||||
* @see GenericAnnotationScanner |
||||
*/ |
||||
public interface AnnotationScanner { |
||||
|
||||
// ============================ 预置的扫描器实例 ============================
|
||||
|
||||
/** |
||||
* 不扫描任何注解 |
||||
*/ |
||||
AnnotationScanner NOTHING = new EmptyAnnotationScanner(); |
||||
|
||||
/** |
||||
* 扫描元素本身直接声明的注解,包括父类带有{@link Inherited}、被传递到元素上的注解的扫描器 |
||||
*/ |
||||
AnnotationScanner DIRECTLY = new GenericAnnotationScanner(false, false, false); |
||||
|
||||
/** |
||||
* 扫描元素本身直接声明的注解,包括父类带有{@link Inherited}、被传递到元素上的注解,以及这些注解的元注解的扫描器 |
||||
*/ |
||||
AnnotationScanner DIRECTLY_AND_META_ANNOTATION = new GenericAnnotationScanner(true, false, false); |
||||
|
||||
/** |
||||
* 扫描元素本身以及父类的层级结构中声明的注解的扫描器 |
||||
*/ |
||||
AnnotationScanner SUPERCLASS = new GenericAnnotationScanner(false, true, false); |
||||
|
||||
/** |
||||
* 扫描元素本身以及父类的层级结构中声明的注解,以及这些注解的元注解的扫描器 |
||||
*/ |
||||
AnnotationScanner SUPERCLASS_AND_META_ANNOTATION = new GenericAnnotationScanner(true, true, false); |
||||
|
||||
/** |
||||
* 扫描元素本身以及父接口的层级结构中声明的注解的扫描器 |
||||
*/ |
||||
AnnotationScanner INTERFACE = new GenericAnnotationScanner(false, false, true); |
||||
|
||||
/** |
||||
* 扫描元素本身以及父接口的层级结构中声明的注解,以及这些注解的元注解的扫描器 |
||||
*/ |
||||
AnnotationScanner INTERFACE_AND_META_ANNOTATION = new GenericAnnotationScanner(true, false, true); |
||||
|
||||
/** |
||||
* 扫描元素本身以及父类、父接口的层级结构中声明的注解的扫描器 |
||||
*/ |
||||
AnnotationScanner TYPE_HIERARCHY = new GenericAnnotationScanner(false, true, true); |
||||
|
||||
/** |
||||
* 扫描元素本身以及父接口、父接口的层级结构中声明的注解,以及这些注解的元注解的扫描器 |
||||
*/ |
||||
AnnotationScanner TYPE_HIERARCHY_AND_META_ANNOTATION = new GenericAnnotationScanner(true, true, true); |
||||
|
||||
// ============================ 静态方法 ============================
|
||||
|
||||
/** |
||||
* 给定一组扫描器,使用第一个支持处理该类型元素的扫描器获取元素上可能存在的注解 |
||||
* |
||||
* @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission |
||||
* @param scanners 注解扫描器 |
||||
* @return 注解 |
||||
*/ |
||||
static List<Annotation> scanByAnySupported(AnnotatedElement annotatedEle, AnnotationScanner... scanners) { |
||||
if (ObjectUtil.isNull(annotatedEle) && ArrayUtil.isNotEmpty(scanners)) { |
||||
return Collections.emptyList(); |
||||
} |
||||
return Stream.of(scanners) |
||||
.filter(scanner -> scanner.support(annotatedEle)) |
||||
.findFirst() |
||||
.map(scanner -> scanner.getAnnotations(annotatedEle)) |
||||
.orElseGet(Collections::emptyList); |
||||
} |
||||
|
||||
/** |
||||
* 根据指定的扫描器,扫描元素上可能存在的注解 |
||||
* |
||||
* @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission |
||||
* @param scanners 注解扫描器 |
||||
* @return 注解 |
||||
*/ |
||||
static List<Annotation> scanByAllSupported(AnnotatedElement annotatedEle, AnnotationScanner... scanners) { |
||||
if (ObjectUtil.isNull(annotatedEle) && ArrayUtil.isNotEmpty(scanners)) { |
||||
return Collections.emptyList(); |
||||
} |
||||
return Stream.of(scanners) |
||||
.map(scanner -> scanner.getAnnotationsIfSupport(annotatedEle)) |
||||
.flatMap(Collection::stream) |
||||
.collect(Collectors.toList()); |
||||
} |
||||
|
||||
// ============================ 抽象方法 ============================
|
||||
|
||||
/** |
||||
* 判断是否支持扫描该注解元素 |
||||
* |
||||
* @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission |
||||
* @return 是否支持扫描该注解元素 |
||||
*/ |
||||
default boolean support(AnnotatedElement annotatedEle) { |
||||
return false; |
||||
} |
||||
|
||||
/** |
||||
* 获取注解元素上的全部注解。调用该方法前,需要确保调用{@link #support(AnnotatedElement)}返回为true |
||||
* |
||||
* @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission |
||||
* @return 注解 |
||||
*/ |
||||
default List<Annotation> getAnnotations(AnnotatedElement annotatedEle) { |
||||
final List<Annotation> annotations = new ArrayList<>(); |
||||
scan((index, annotation) -> annotations.add(annotation), annotatedEle, null); |
||||
return annotations; |
||||
} |
||||
|
||||
/** |
||||
* 若{@link #support(AnnotatedElement)}返回{@code true}, |
||||
* 则调用并返回{@link #getAnnotations(AnnotatedElement)}结果, |
||||
* 否则返回{@link Collections#emptyList()} |
||||
* |
||||
* @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission |
||||
* @return 注解 |
||||
*/ |
||||
default List<Annotation> getAnnotationsIfSupport(AnnotatedElement annotatedEle) { |
||||
return support(annotatedEle) ? getAnnotations(annotatedEle) : Collections.emptyList(); |
||||
} |
||||
|
||||
/** |
||||
* 扫描注解元素的层级结构(若存在),然后对获取到的注解和注解对应的层级索引进行处理。 |
||||
* 调用该方法前,需要确保调用{@link #support(AnnotatedElement)}返回为true |
||||
* |
||||
* @param consumer 对获取到的注解和注解对应的层级索引的处理 |
||||
* @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission |
||||
* @param filter 注解过滤器,无法通过过滤器的注解不会被处理。该参数允许为空。 |
||||
*/ |
||||
default void scan(BiConsumer<Integer, Annotation> consumer, AnnotatedElement annotatedEle, Predicate<Annotation> filter) { |
||||
filter = ObjectUtil.defaultIfNull(filter, (a)->annotation -> true); |
||||
for (final Annotation annotation : annotatedEle.getAnnotations()) { |
||||
if (AnnotationUtil.isNotJdkMateAnnotation(annotation.annotationType()) && filter.test(annotation)) { |
||||
consumer.accept(0, annotation); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 若{@link #support(AnnotatedElement)}返回{@code true},则调用{@link #scan(BiConsumer, AnnotatedElement, Predicate)} |
||||
* |
||||
* @param consumer 对获取到的注解和注解对应的层级索引的处理 |
||||
* @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission |
||||
* @param filter 注解过滤器,无法通过过滤器的注解不会被处理。该参数允许为空。 |
||||
*/ |
||||
default void scanIfSupport(BiConsumer<Integer, Annotation> consumer, AnnotatedElement annotatedEle, Predicate<Annotation> filter) { |
||||
if (support(annotatedEle)) { |
||||
scan(consumer, annotatedEle, filter); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,44 @@ |
||||
package cn.hutool.core.annotation.scanner; |
||||
|
||||
import cn.hutool.core.util.ObjectUtil; |
||||
|
||||
import java.lang.annotation.Annotation; |
||||
import java.lang.reflect.AnnotatedElement; |
||||
import java.util.function.BiConsumer; |
||||
import java.util.function.Predicate; |
||||
import java.util.stream.Stream; |
||||
|
||||
/** |
||||
* 扫描{@link AnnotatedElement}上的注解,不支持处理层级对象 |
||||
* |
||||
* @author huangchengxing |
||||
*/ |
||||
public class ElementAnnotationScanner implements AnnotationScanner { |
||||
|
||||
/** |
||||
* 判断是否支持扫描该注解元素,仅当注解元素不为空时返回{@code true} |
||||
* |
||||
* @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission |
||||
* @return 是否支持扫描该注解元素 |
||||
*/ |
||||
@Override |
||||
public boolean support(AnnotatedElement annotatedEle) { |
||||
return ObjectUtil.isNotNull(annotatedEle); |
||||
} |
||||
|
||||
/** |
||||
* 扫描{@link AnnotatedElement}上直接声明的注解,调用前需要确保调用{@link #support(AnnotatedElement)}返回为true |
||||
* |
||||
* @param consumer 对获取到的注解和注解对应的层级索引的处理 |
||||
* @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission |
||||
* @param filter 注解过滤器,无法通过过滤器的注解不会被处理。该参数允许为空。 |
||||
*/ |
||||
@Override |
||||
public void scan(BiConsumer<Integer, Annotation> consumer, AnnotatedElement annotatedEle, Predicate<Annotation> filter) { |
||||
filter = ObjectUtil.defaultIfNull(filter,a-> t -> true); |
||||
Stream.of(annotatedEle.getAnnotations()) |
||||
.filter(filter) |
||||
.forEach(annotation -> consumer.accept(0, annotation)); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,31 @@ |
||||
package cn.hutool.core.annotation.scanner; |
||||
|
||||
import java.lang.annotation.Annotation; |
||||
import java.lang.reflect.AnnotatedElement; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
import java.util.function.BiConsumer; |
||||
import java.util.function.Predicate; |
||||
|
||||
/** |
||||
* 默认不扫描任何元素的扫描器 |
||||
* |
||||
* @author huangchengxing |
||||
*/ |
||||
public class EmptyAnnotationScanner implements AnnotationScanner { |
||||
|
||||
@Override |
||||
public boolean support(AnnotatedElement annotatedEle) { |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public List<Annotation> getAnnotations(AnnotatedElement annotatedEle) { |
||||
return Collections.emptyList(); |
||||
} |
||||
|
||||
@Override |
||||
public void scan(BiConsumer<Integer, Annotation> consumer, AnnotatedElement annotatedEle, Predicate<Annotation> filter) { |
||||
// do nothing
|
||||
} |
||||
} |
@ -0,0 +1,47 @@ |
||||
package cn.hutool.core.annotation.scanner; |
||||
|
||||
import cn.hutool.core.annotation.AnnotationUtil; |
||||
import cn.hutool.core.util.ObjectUtil; |
||||
|
||||
import java.lang.annotation.Annotation; |
||||
import java.lang.reflect.AnnotatedElement; |
||||
import java.lang.reflect.Field; |
||||
import java.util.function.BiConsumer; |
||||
import java.util.function.Predicate; |
||||
|
||||
/** |
||||
* 扫描{@link Field}上的注解 |
||||
* |
||||
* @author huangchengxing |
||||
*/ |
||||
public class FieldAnnotationScanner implements AnnotationScanner { |
||||
|
||||
/** |
||||
* 判断是否支持扫描该注解元素,仅当注解元素是{@link Field}时返回{@code true} |
||||
* |
||||
* @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission |
||||
* @return 是否支持扫描该注解元素 |
||||
*/ |
||||
@Override |
||||
public boolean support(AnnotatedElement annotatedEle) { |
||||
return annotatedEle instanceof Field; |
||||
} |
||||
|
||||
/** |
||||
* 扫描{@link Field}上直接声明的注解,调用前需要确保调用{@link #support(AnnotatedElement)}返回为true |
||||
* |
||||
* @param consumer 对获取到的注解和注解对应的层级索引的处理 |
||||
* @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission |
||||
* @param filter 注解过滤器,无法通过过滤器的注解不会被处理。该参数允许为空。 |
||||
*/ |
||||
@Override |
||||
public void scan(BiConsumer<Integer, Annotation> consumer, AnnotatedElement annotatedEle, Predicate<Annotation> filter) { |
||||
filter = ObjectUtil.defaultIfNull(filter, a -> annotation -> true); |
||||
for (final Annotation annotation : annotatedEle.getAnnotations()) { |
||||
if (AnnotationUtil.isNotJdkMateAnnotation(annotation.annotationType()) && filter.test(annotation)) { |
||||
consumer.accept(0, annotation); |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,149 @@ |
||||
package cn.hutool.core.annotation.scanner; |
||||
|
||||
import cn.hutool.core.map.multi.ListValueMap; |
||||
import cn.hutool.core.util.ObjectUtil; |
||||
|
||||
import java.lang.annotation.Annotation; |
||||
import java.lang.reflect.AnnotatedElement; |
||||
import java.lang.reflect.Method; |
||||
import java.util.Collections; |
||||
import java.util.LinkedHashMap; |
||||
import java.util.function.BiConsumer; |
||||
import java.util.function.Predicate; |
||||
|
||||
/** |
||||
* <p>通用注解扫描器,支持按不同的层级结构扫描{@link AnnotatedElement}上的注解。 |
||||
* |
||||
* <p>当{@link AnnotatedElement}类型不同时,“层级结构”指向的对象将有所区别: |
||||
* <ul> |
||||
* <li> |
||||
* 当元素为{@link Method}时,此处层级结构指声明方法的类的层级结构, |
||||
* 扫描器将从层级结构中寻找与该方法签名相同的方法,并对其进行扫描; |
||||
* </li> |
||||
* <li> |
||||
* 当元素为{@link Class}时,此处层级结构即指类本身与其父类、父接口共同构成的层级结构, |
||||
* 扫描器将扫描层级结构中类、接口声明的注解; |
||||
* </li> |
||||
* <li>当元素不为{@link Method}或{@link Class}时,则其层级结构仅有其本身一层;</li> |
||||
* </ul> |
||||
* 此外,扫描器支持在获取到层级结构中的注解对象后,再对注解对象的元注解进行扫描。 |
||||
* |
||||
* @author huangchengxing |
||||
* @see TypeAnnotationScanner |
||||
* @see MethodAnnotationScanner |
||||
* @see MetaAnnotationScanner |
||||
* @see ElementAnnotationScanner |
||||
*/ |
||||
public class GenericAnnotationScanner implements AnnotationScanner { |
||||
|
||||
/** |
||||
* 类型扫描器 |
||||
*/ |
||||
private final AnnotationScanner typeScanner; |
||||
|
||||
/** |
||||
* 方法扫描器 |
||||
*/ |
||||
private final AnnotationScanner methodScanner; |
||||
|
||||
/** |
||||
* 元注解扫描器 |
||||
*/ |
||||
private final AnnotationScanner metaScanner; |
||||
|
||||
/** |
||||
* 普通元素扫描器 |
||||
*/ |
||||
private final AnnotationScanner elementScanner; |
||||
|
||||
/** |
||||
* 通用注解扫描器支持扫描所有类型的{@link AnnotatedElement} |
||||
* |
||||
* @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission |
||||
* @return 是否支持扫描该注解元素 |
||||
*/ |
||||
@Override |
||||
public boolean support(AnnotatedElement annotatedEle) { |
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* 构造一个通用注解扫描器 |
||||
* |
||||
* @param enableScanMetaAnnotation 是否扫描注解上的元注解 |
||||
* @param enableScanSupperClass 是否扫描父类 |
||||
* @param enableScanSupperInterface 是否扫描父接口 |
||||
*/ |
||||
public GenericAnnotationScanner( |
||||
boolean enableScanMetaAnnotation, |
||||
boolean enableScanSupperClass, |
||||
boolean enableScanSupperInterface) { |
||||
|
||||
this.metaScanner = enableScanMetaAnnotation ? new MetaAnnotationScanner() : new EmptyAnnotationScanner(); |
||||
this.typeScanner = new TypeAnnotationScanner( |
||||
enableScanSupperClass, enableScanSupperInterface, a -> true, Collections.emptySet() |
||||
); |
||||
this.methodScanner = new MethodAnnotationScanner( |
||||
enableScanSupperClass, enableScanSupperInterface, a -> true, Collections.emptySet() |
||||
); |
||||
this.elementScanner = new ElementAnnotationScanner(); |
||||
} |
||||
|
||||
/** |
||||
* 扫描注解元素的层级结构(若存在),然后对获取到的注解和注解对应的层级索引进行处理 |
||||
* |
||||
* @param consumer 对获取到的注解和注解对应的层级索引的处理 |
||||
* @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission |
||||
* @param filter 注解过滤器,无法通过过滤器的注解不会被处理。该参数允许为空。 |
||||
*/ |
||||
@Override |
||||
public void scan(BiConsumer<Integer, Annotation> consumer, AnnotatedElement annotatedEle, Predicate<Annotation> filter) { |
||||
filter = ObjectUtil.defaultIfNull(filter, a -> t -> true); |
||||
if (ObjectUtil.isNull(annotatedEle)) { |
||||
return; |
||||
} |
||||
// 注解元素是类
|
||||
if (annotatedEle instanceof Class) { |
||||
scanElements(typeScanner, consumer, annotatedEle, filter); |
||||
} |
||||
// 注解元素是方法
|
||||
else if (annotatedEle instanceof Method) { |
||||
scanElements(methodScanner, consumer, annotatedEle, filter); |
||||
} |
||||
// 注解元素是其他类型
|
||||
else { |
||||
scanElements(elementScanner, consumer, annotatedEle, filter); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 扫描注解类的层级结构(若存在),然后对获取到的注解和注解对应的层级索引进行处理 |
||||
* |
||||
* @param scanner 使用的扫描器 |
||||
* @param consumer 对获取到的注解和注解对应的层级索引的处理 |
||||
* @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission |
||||
* @param filter 注解过滤器,无法通过过滤器的注解不会被处理。该参数允许为空。 |
||||
*/ |
||||
private void scanElements( |
||||
AnnotationScanner scanner, |
||||
BiConsumer<Integer, Annotation> consumer, |
||||
AnnotatedElement annotatedEle, |
||||
Predicate<Annotation> filter) { |
||||
// 扫描类上注解
|
||||
final ListValueMap<Integer, Annotation> classAnnotations = new ListValueMap<>(new LinkedHashMap<>()); |
||||
scanner.scan((index, annotation) -> { |
||||
if (filter.test(annotation)) { |
||||
classAnnotations.putValue(index, annotation); |
||||
} |
||||
}, annotatedEle, filter); |
||||
|
||||
// 扫描元注解
|
||||
classAnnotations.forEach((index, annotations) -> |
||||
annotations.forEach(annotation -> { |
||||
consumer.accept(index, annotation); |
||||
metaScanner.scan(consumer, annotation.annotationType(), filter); |
||||
}) |
||||
); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,110 @@ |
||||
package cn.hutool.core.annotation.scanner; |
||||
|
||||
import cn.hutool.core.annotation.AnnotationUtil; |
||||
import cn.hutool.core.collection.CollUtil; |
||||
import cn.hutool.core.util.ClassUtil; |
||||
import cn.hutool.core.util.ObjectUtil; |
||||
|
||||
import java.lang.annotation.Annotation; |
||||
import java.lang.reflect.AnnotatedElement; |
||||
import java.util.*; |
||||
import java.util.function.BiConsumer; |
||||
import java.util.function.Predicate; |
||||
import java.util.stream.Collectors; |
||||
import java.util.stream.Stream; |
||||
|
||||
/** |
||||
* 扫描注解类上存在的注解,支持处理枚举实例或枚举类型 |
||||
* 需要注意,当待解析是枚举类时,有可能与{@link TypeAnnotationScanner}冲突 |
||||
* |
||||
* @author huangchengxing |
||||
* @see TypeAnnotationScanner |
||||
*/ |
||||
public class MetaAnnotationScanner implements AnnotationScanner { |
||||
|
||||
/** |
||||
* 获取当前注解的元注解后,是否继续递归扫描的元注解的元注解 |
||||
*/ |
||||
private final boolean includeSupperMetaAnnotation; |
||||
|
||||
/** |
||||
* 构造一个元注解扫描器 |
||||
* |
||||
* @param includeSupperMetaAnnotation 获取当前注解的元注解后,是否继续递归扫描的元注解的元注解 |
||||
*/ |
||||
public MetaAnnotationScanner(boolean includeSupperMetaAnnotation) { |
||||
this.includeSupperMetaAnnotation = includeSupperMetaAnnotation; |
||||
} |
||||
|
||||
/** |
||||
* 构造一个元注解扫描器,默认在扫描当前注解上的元注解后,并继续递归扫描元注解 |
||||
*/ |
||||
public MetaAnnotationScanner() { |
||||
this(true); |
||||
} |
||||
|
||||
/** |
||||
* 判断是否支持扫描该注解元素,仅当注解元素是{@link Annotation}接口的子类{@link Class}时返回{@code true} |
||||
* |
||||
* @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission |
||||
* @return 是否支持扫描该注解元素 |
||||
*/ |
||||
@Override |
||||
public boolean support(AnnotatedElement annotatedEle) { |
||||
return (annotatedEle instanceof Class && ClassUtil.isAssignable(Annotation.class, (Class<?>) annotatedEle)); |
||||
} |
||||
|
||||
/** |
||||
* 获取注解元素上的全部注解。调用该方法前,需要确保调用{@link #support(AnnotatedElement)}返回为true |
||||
* |
||||
* @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission |
||||
* @return 注解 |
||||
*/ |
||||
@Override |
||||
public List<Annotation> getAnnotations(AnnotatedElement annotatedEle) { |
||||
final List<Annotation> annotations = new ArrayList<>(); |
||||
scan( |
||||
(index, annotation) -> annotations.add(annotation), annotatedEle, |
||||
annotation -> ObjectUtil.notEqual(annotation, annotatedEle) |
||||
); |
||||
return annotations; |
||||
} |
||||
|
||||
/** |
||||
* 按广度优先扫描指定注解上的元注解,对扫描到的注解与层级索引进行操作 |
||||
* |
||||
* @param consumer 当前层级索引与操作 |
||||
* @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission |
||||
* @param filter 过滤器 |
||||
*/ |
||||
@SuppressWarnings("unchecked") |
||||
@Override |
||||
public void scan(BiConsumer<Integer, Annotation> consumer, AnnotatedElement annotatedEle, Predicate<Annotation> filter) { |
||||
filter = ObjectUtil.defaultIfNull(filter, a -> t -> true); |
||||
Set<Class<? extends Annotation>> accessed = new HashSet<>(); |
||||
final Deque<List<Class<? extends Annotation>>> deque = CollUtil.newLinkedList(CollUtil.newArrayList((Class<? extends Annotation>) annotatedEle)); |
||||
int distance = 0; |
||||
do { |
||||
final List<Class<? extends Annotation>> annotationTypes = deque.removeFirst(); |
||||
for (final Class<? extends Annotation> type : annotationTypes) { |
||||
final List<Annotation> metaAnnotations = Stream.of(type.getAnnotations()) |
||||
.filter(a -> !AnnotationUtil.isJdkMetaAnnotation(a.annotationType())) |
||||
.filter(filter) |
||||
.collect(Collectors.toList()); |
||||
for (final Annotation metaAnnotation : metaAnnotations) { |
||||
consumer.accept(distance, metaAnnotation); |
||||
} |
||||
accessed.add(type); |
||||
List<Class<? extends Annotation>> next = metaAnnotations.stream() |
||||
.map(Annotation::annotationType) |
||||
.filter(t -> !accessed.contains(t)) |
||||
.collect(Collectors.toList()); |
||||
if (CollUtil.isNotEmpty(next)) { |
||||
deque.addLast(next); |
||||
} |
||||
} |
||||
distance++; |
||||
} while (includeSupperMetaAnnotation && !deque.isEmpty()); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,133 @@ |
||||
package cn.hutool.core.annotation.scanner; |
||||
|
||||
import cn.hutool.core.collection.CollUtil; |
||||
import cn.hutool.core.util.ArrayUtil; |
||||
import cn.hutool.core.util.ClassUtil; |
||||
import cn.hutool.core.util.StrUtil; |
||||
|
||||
import java.lang.annotation.Annotation; |
||||
import java.lang.reflect.AnnotatedElement; |
||||
import java.lang.reflect.Method; |
||||
import java.util.Set; |
||||
import java.util.function.Predicate; |
||||
import java.util.stream.Stream; |
||||
|
||||
/** |
||||
* 扫描{@link Method}上的注解 |
||||
* |
||||
* @author huangchengxing |
||||
*/ |
||||
public class MethodAnnotationScanner extends AbstractTypeAnnotationScanner<MethodAnnotationScanner> implements AnnotationScanner { |
||||
|
||||
/** |
||||
* 构造一个类注解扫描器,仅扫描该方法上直接声明的注解 |
||||
*/ |
||||
public MethodAnnotationScanner() { |
||||
this(false); |
||||
} |
||||
|
||||
/** |
||||
* 构造一个类注解扫描器 |
||||
* |
||||
* @param scanSameSignatureMethod 是否扫描类层级结构中具有相同方法签名的方法 |
||||
*/ |
||||
public MethodAnnotationScanner(boolean scanSameSignatureMethod) { |
||||
this(scanSameSignatureMethod, targetClass -> true, CollUtil.newLinkedHashSet()); |
||||
} |
||||
|
||||
/** |
||||
* 构造一个方法注解扫描器 |
||||
* |
||||
* @param scanSameSignatureMethod 是否扫描类层级结构中具有相同方法签名的方法 |
||||
* @param filter 过滤器 |
||||
* @param excludeTypes 不包含的类型 |
||||
*/ |
||||
public MethodAnnotationScanner(boolean scanSameSignatureMethod, Predicate<Class<?>> filter, Set<Class<?>> excludeTypes) { |
||||
super(scanSameSignatureMethod, scanSameSignatureMethod, filter, excludeTypes); |
||||
} |
||||
|
||||
/** |
||||
* 构造一个方法注解扫描器 |
||||
* |
||||
* @param includeSuperClass 是否允许扫描父类中具有相同方法签名的方法 |
||||
* @param includeInterfaces 是否允许扫描父接口中具有相同方法签名的方法 |
||||
* @param filter 过滤器 |
||||
* @param excludeTypes 不包含的类型 |
||||
*/ |
||||
public MethodAnnotationScanner(boolean includeSuperClass, boolean includeInterfaces, Predicate<Class<?>> filter, Set<Class<?>> excludeTypes) { |
||||
super(includeSuperClass, includeInterfaces, filter, excludeTypes); |
||||
} |
||||
|
||||
/** |
||||
* 判断是否支持扫描该注解元素,仅当注解元素是{@link Method}时返回{@code true} |
||||
* |
||||
* @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission |
||||
* @return boolean 是否支持扫描该注解元素 |
||||
*/ |
||||
@Override |
||||
public boolean support(AnnotatedElement annotatedEle) { |
||||
return annotatedEle instanceof Method; |
||||
} |
||||
|
||||
/** |
||||
* 获取声明该方法的类 |
||||
* |
||||
* @param annotatedElement 注解元素 |
||||
* @return 要递归的类型 |
||||
* @see Method#getDeclaringClass() |
||||
*/ |
||||
@Override |
||||
protected Class<?> getClassFormAnnotatedElement(AnnotatedElement annotatedElement) { |
||||
return ((Method)annotatedElement).getDeclaringClass(); |
||||
} |
||||
|
||||
/** |
||||
* 若父类/父接口中方法具有相同的方法签名,则返回该方法上的注解 |
||||
* |
||||
* @param source 原始方法 |
||||
* @param index 类的层级索引 |
||||
* @param targetClass 类 |
||||
* @return 最终所需的目标注解 |
||||
*/ |
||||
@Override |
||||
protected Annotation[] getAnnotationsFromTargetClass(AnnotatedElement source, int index, Class<?> targetClass) { |
||||
final Method sourceMethod = (Method) source; |
||||
return Stream.of(ClassUtil.getDeclaredMethods(targetClass)) |
||||
.filter(superMethod -> !superMethod.isBridge()) |
||||
.filter(superMethod -> hasSameSignature(sourceMethod, superMethod)) |
||||
.map(AnnotatedElement::getAnnotations) |
||||
.flatMap(Stream::of) |
||||
.toArray(Annotation[]::new); |
||||
} |
||||
|
||||
/** |
||||
* 设置是否扫描类层级结构中具有相同方法签名的方法 |
||||
* |
||||
* @param scanSuperMethodIfOverride 是否扫描类层级结构中具有相同方法签名的方法 |
||||
* @return 当前实例 |
||||
*/ |
||||
public MethodAnnotationScanner setScanSameSignatureMethod(boolean scanSuperMethodIfOverride) { |
||||
setIncludeInterfaces(scanSuperMethodIfOverride); |
||||
setIncludeSuperClass(scanSuperMethodIfOverride); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 该方法是否具备与扫描的方法相同的方法签名 |
||||
*/ |
||||
private boolean hasSameSignature(Method sourceMethod, Method superMethod) { |
||||
if (!StrUtil.equals(sourceMethod.getName(), superMethod.getName())) { |
||||
return false; |
||||
} |
||||
final Class<?>[] sourceParameterTypes = sourceMethod.getParameterTypes(); |
||||
final Class<?>[] targetParameterTypes = superMethod.getParameterTypes(); |
||||
if (sourceParameterTypes.length != targetParameterTypes.length) { |
||||
return false; |
||||
} |
||||
if (!ArrayUtil.containsAll(sourceParameterTypes, targetParameterTypes)) { |
||||
return false; |
||||
} |
||||
return ClassUtil.isAssignable(superMethod.getReturnType(), sourceMethod.getReturnType()); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,105 @@ |
||||
package cn.hutool.core.annotation.scanner; |
||||
|
||||
import cn.hutool.core.collection.CollUtil; |
||||
|
||||
import java.lang.annotation.Annotation; |
||||
import java.lang.reflect.AnnotatedElement; |
||||
import java.lang.reflect.Proxy; |
||||
import java.util.Set; |
||||
import java.util.function.Predicate; |
||||
import java.util.function.UnaryOperator; |
||||
|
||||
/** |
||||
* 扫描{@link Class}上的注解 |
||||
* |
||||
* @author huangchengxing |
||||
*/ |
||||
public class TypeAnnotationScanner extends AbstractTypeAnnotationScanner<TypeAnnotationScanner> implements AnnotationScanner { |
||||
|
||||
/** |
||||
* 构造一个类注解扫描器 |
||||
* |
||||
* @param includeSupperClass 是否允许扫描父类 |
||||
* @param includeInterfaces 是否允许扫描父接口 |
||||
* @param filter 过滤器 |
||||
* @param excludeTypes 不包含的类型 |
||||
*/ |
||||
public TypeAnnotationScanner(boolean includeSupperClass, boolean includeInterfaces, Predicate<Class<?>> filter, Set<Class<?>> excludeTypes) { |
||||
super(includeSupperClass, includeInterfaces, filter, excludeTypes); |
||||
} |
||||
|
||||
/** |
||||
* 构建一个类注解扫描器,默认允许扫描指定元素的父类以及父接口 |
||||
*/ |
||||
public TypeAnnotationScanner() { |
||||
this(true, true, t -> true, CollUtil.newLinkedHashSet()); |
||||
} |
||||
|
||||
/** |
||||
* 判断是否支持扫描该注解元素,仅当注解元素是{@link Class}接时返回{@code true} |
||||
* |
||||
* @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission |
||||
* @return 是否支持扫描该注解元素 |
||||
*/ |
||||
@Override |
||||
public boolean support(AnnotatedElement annotatedEle) { |
||||
return annotatedEle instanceof Class; |
||||
} |
||||
|
||||
/** |
||||
* 将注解元素转为{@link Class} |
||||
* |
||||
* @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission |
||||
* @return 要递归的类型 |
||||
*/ |
||||
@Override |
||||
protected Class<?> getClassFormAnnotatedElement(AnnotatedElement annotatedEle) { |
||||
return (Class<?>)annotatedEle; |
||||
} |
||||
|
||||
/** |
||||
* 获取{@link Class#getAnnotations()} |
||||
* |
||||
* @param source 最初的注解元素 |
||||
* @param index 类的层级索引 |
||||
* @param targetClass 类 |
||||
* @return 类上直接声明的注解 |
||||
*/ |
||||
@Override |
||||
protected Annotation[] getAnnotationsFromTargetClass(AnnotatedElement source, int index, Class<?> targetClass) { |
||||
return targetClass.getAnnotations(); |
||||
} |
||||
|
||||
/** |
||||
* 是否允许扫描父类 |
||||
* |
||||
* @param includeSuperClass 是否允许扫描父类 |
||||
* @return 当前实例 |
||||
*/ |
||||
@Override |
||||
public TypeAnnotationScanner setIncludeSuperClass(boolean includeSuperClass) { |
||||
return super.setIncludeSuperClass(includeSuperClass); |
||||
} |
||||
|
||||
/** |
||||
* 是否允许扫描父接口 |
||||
* |
||||
* @param includeInterfaces 是否允许扫描父类 |
||||
* @return 当前实例 |
||||
*/ |
||||
@Override |
||||
public TypeAnnotationScanner setIncludeInterfaces(boolean includeInterfaces) { |
||||
return super.setIncludeInterfaces(includeInterfaces); |
||||
} |
||||
|
||||
/** |
||||
* 若类型为jdk代理类,则尝试转换为原始被代理类 |
||||
*/ |
||||
public static class JdkProxyClassConverter implements UnaryOperator<Class<?>> { |
||||
@Override |
||||
public Class<?> apply(Class<?> sourceClass) { |
||||
return Proxy.isProxyClass(sourceClass) ? apply(sourceClass.getSuperclass()) : sourceClass; |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,7 @@ |
||||
/** |
||||
* 注解包扫描封装 |
||||
* |
||||
* @author looly |
||||
* |
||||
*/ |
||||
package cn.hutool.core.annotation.scanner; |
@ -0,0 +1,324 @@ |
||||
package cn.hutool.core.bean; |
||||
|
||||
import cn.hutool.core.lang.Assert; |
||||
import cn.hutool.core.map.CaseInsensitiveMap; |
||||
import cn.hutool.core.util.BooleanUtil; |
||||
import cn.hutool.core.util.ModifierUtil; |
||||
import cn.hutool.core.util.ReflectUtil; |
||||
import cn.hutool.core.util.StrUtil; |
||||
|
||||
import java.io.Serializable; |
||||
import java.lang.reflect.Field; |
||||
import java.lang.reflect.Method; |
||||
import java.util.Collection; |
||||
import java.util.LinkedHashMap; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* Bean信息描述做为BeanInfo替代方案,此对象持有JavaBean中的setters和getters等相关信息描述<br> |
||||
* 查找Getter和Setter方法时会: |
||||
* |
||||
* <pre> |
||||
* 1. 忽略字段和方法名的大小写 |
||||
* 2. Getter查找getXXX、isXXX、getIsXXX |
||||
* 3. Setter查找setXXX、setIsXXX |
||||
* 4. Setter忽略参数值与字段值不匹配的情况,因此有多个参数类型的重载时,会调用首次匹配的 |
||||
* </pre> |
||||
* |
||||
* @author looly |
||||
* @since 3.1.2 |
||||
*/ |
||||
public class BeanDesc implements Serializable { |
||||
private static final long serialVersionUID = 1L; |
||||
|
||||
/** |
||||
* Bean类 |
||||
*/ |
||||
private final Class<?> beanClass; |
||||
/** |
||||
* 属性Map |
||||
*/ |
||||
private final Map<String, PropDesc> propMap = new LinkedHashMap<>(); |
||||
|
||||
/** |
||||
* 构造 |
||||
* |
||||
* @param beanClass Bean类 |
||||
*/ |
||||
public BeanDesc(Class<?> beanClass) { |
||||
Assert.notNull(beanClass); |
||||
this.beanClass = beanClass; |
||||
init(); |
||||
} |
||||
|
||||
/** |
||||
* 获取Bean的全类名 |
||||
* |
||||
* @return Bean的类名 |
||||
*/ |
||||
public String getName() { |
||||
return this.beanClass.getName(); |
||||
} |
||||
|
||||
/** |
||||
* 获取Bean的简单类名 |
||||
* |
||||
* @return Bean的类名 |
||||
*/ |
||||
public String getSimpleName() { |
||||
return this.beanClass.getSimpleName(); |
||||
} |
||||
|
||||
/** |
||||
* 获取字段名-字段属性Map |
||||
* |
||||
* @param ignoreCase 是否忽略大小写,true为忽略,false不忽略 |
||||
* @return 字段名-字段属性Map |
||||
*/ |
||||
public Map<String, PropDesc> getPropMap(boolean ignoreCase) { |
||||
return ignoreCase ? new CaseInsensitiveMap<>(1, this.propMap) : this.propMap; |
||||
} |
||||
|
||||
/** |
||||
* 获取字段属性列表 |
||||
* |
||||
* @return {@link PropDesc} 列表 |
||||
*/ |
||||
public Collection<PropDesc> getProps() { |
||||
return this.propMap.values(); |
||||
} |
||||
|
||||
/** |
||||
* 获取属性,如果不存在返回null |
||||
* |
||||
* @param fieldName 字段名 |
||||
* @return {@link PropDesc} |
||||
*/ |
||||
public PropDesc getProp(String fieldName) { |
||||
return this.propMap.get(fieldName); |
||||
} |
||||
|
||||
/** |
||||
* 获得字段名对应的字段对象,如果不存在返回null |
||||
* |
||||
* @param fieldName 字段名 |
||||
* @return 字段值 |
||||
*/ |
||||
public Field getField(String fieldName) { |
||||
final PropDesc desc = this.propMap.get(fieldName); |
||||
return null == desc ? null : desc.getField(); |
||||
} |
||||
|
||||
/** |
||||
* 获取Getter方法,如果不存在返回null |
||||
* |
||||
* @param fieldName 字段名 |
||||
* @return Getter方法 |
||||
*/ |
||||
public Method getGetter(String fieldName) { |
||||
final PropDesc desc = this.propMap.get(fieldName); |
||||
return null == desc ? null : desc.getGetter(); |
||||
} |
||||
|
||||
/** |
||||
* 获取Setter方法,如果不存在返回null |
||||
* |
||||
* @param fieldName 字段名 |
||||
* @return Setter方法 |
||||
*/ |
||||
public Method getSetter(String fieldName) { |
||||
final PropDesc desc = this.propMap.get(fieldName); |
||||
return null == desc ? null : desc.getSetter(); |
||||
} |
||||
|
||||
// ------------------------------------------------------------------------------------------------------ Private method start
|
||||
|
||||
/** |
||||
* 初始化<br> |
||||
* 只有与属性关联的相关Getter和Setter方法才会被读取,无关的getXXX和setXXX都被忽略 |
||||
* |
||||
* @return this |
||||
*/ |
||||
private BeanDesc init() { |
||||
final Method[] gettersAndSetters = ReflectUtil.getMethods(this.beanClass, ReflectUtil::isGetterOrSetterIgnoreCase); |
||||
PropDesc prop; |
||||
for (Field field : ReflectUtil.getFields(this.beanClass)) { |
||||
// 排除静态属性和对象子类
|
||||
if (!ModifierUtil.isStatic(field) && !ReflectUtil.isOuterClassField(field)) { |
||||
prop = createProp(field, gettersAndSetters); |
||||
// 只有不存在时才放入,防止父类属性覆盖子类属性
|
||||
this.propMap.putIfAbsent(prop.getFieldName(), prop); |
||||
} |
||||
} |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 根据字段创建属性描述<br> |
||||
* 查找Getter和Setter方法时会: |
||||
* |
||||
* <pre> |
||||
* 1. 忽略字段和方法名的大小写 |
||||
* 2. Getter查找getXXX、isXXX、getIsXXX |
||||
* 3. Setter查找setXXX、setIsXXX |
||||
* 4. Setter忽略参数值与字段值不匹配的情况,因此有多个参数类型的重载时,会调用首次匹配的 |
||||
* </pre> |
||||
* |
||||
* @param field 字段 |
||||
* @param methods 类中所有的方法 |
||||
* @return {@link PropDesc} |
||||
* @since 4.0.2 |
||||
*/ |
||||
private PropDesc createProp(Field field, Method[] methods) { |
||||
final PropDesc prop = findProp(field, methods, false); |
||||
// 忽略大小写重新匹配一次
|
||||
if (null == prop.getter || null == prop.setter) { |
||||
final PropDesc propIgnoreCase = findProp(field, methods, true); |
||||
if (null == prop.getter) { |
||||
prop.getter = propIgnoreCase.getter; |
||||
} |
||||
if (null == prop.setter) { |
||||
prop.setter = propIgnoreCase.setter; |
||||
} |
||||
} |
||||
|
||||
return prop; |
||||
} |
||||
|
||||
/** |
||||
* 查找字段对应的Getter和Setter方法 |
||||
* |
||||
* @param field 字段 |
||||
* @param gettersOrSetters 类中所有的Getter或Setter方法 |
||||
* @param ignoreCase 是否忽略大小写匹配 |
||||
* @return PropDesc |
||||
*/ |
||||
private PropDesc findProp(Field field, Method[] gettersOrSetters, boolean ignoreCase) { |
||||
final String fieldName = field.getName(); |
||||
final Class<?> fieldType = field.getType(); |
||||
final boolean isBooleanField = BooleanUtil.isBoolean(fieldType); |
||||
|
||||
Method getter = null; |
||||
Method setter = null; |
||||
String methodName; |
||||
for (Method method : gettersOrSetters) { |
||||
methodName = method.getName(); |
||||
if (method.getParameterCount() == 0) { |
||||
// 无参数,可能为Getter方法
|
||||
if (isMatchGetter(methodName, fieldName, isBooleanField, ignoreCase)) { |
||||
// 方法名与字段名匹配,则为Getter方法
|
||||
getter = method; |
||||
} |
||||
} else if (isMatchSetter(methodName, fieldName, isBooleanField, ignoreCase)) { |
||||
// setter方法的参数类型和字段类型必须一致,或参数类型是字段类型的子类
|
||||
if(fieldType.isAssignableFrom(method.getParameterTypes()[0])){ |
||||
setter = method; |
||||
} |
||||
} |
||||
if (null != getter && null != setter) { |
||||
// 如果Getter和Setter方法都找到了,不再继续寻找
|
||||
break; |
||||
} |
||||
} |
||||
|
||||
return new PropDesc(field, getter, setter); |
||||
} |
||||
|
||||
/** |
||||
* 方法是否为Getter方法<br> |
||||
* 匹配规则如下(忽略大小写): |
||||
* |
||||
* <pre> |
||||
* 字段名 -》 方法名 |
||||
* isName -》 isName |
||||
* isName -》 isIsName |
||||
* isName -》 getIsName |
||||
* name -》 isName |
||||
* name -》 getName |
||||
* </pre> |
||||
* |
||||
* @param methodName 方法名 |
||||
* @param fieldName 字段名 |
||||
* @param isBooleanField 是否为Boolean类型字段 |
||||
* @param ignoreCase 匹配是否忽略大小写 |
||||
* @return 是否匹配 |
||||
*/ |
||||
private boolean isMatchGetter(String methodName, String fieldName, boolean isBooleanField, boolean ignoreCase) { |
||||
final String handledFieldName; |
||||
if (ignoreCase) { |
||||
// 全部转为小写,忽略大小写比较
|
||||
methodName = methodName.toLowerCase(); |
||||
handledFieldName = fieldName.toLowerCase(); |
||||
fieldName = handledFieldName; |
||||
} else { |
||||
handledFieldName = StrUtil.upperFirst(fieldName); |
||||
} |
||||
|
||||
// 针对Boolean类型特殊检查
|
||||
if (isBooleanField) { |
||||
if (fieldName.startsWith("is")) { |
||||
// 字段已经是is开头
|
||||
if (methodName.equals(fieldName) // isName -》 isName
|
||||
|| ("get" + handledFieldName).equals(methodName)// isName -》 getIsName
|
||||
|| ("is" + handledFieldName).equals(methodName)// isName -》 isIsName
|
||||
) { |
||||
return true; |
||||
} |
||||
} else if (("is" + handledFieldName).equals(methodName)) { |
||||
// 字段非is开头, name -》 isName
|
||||
return true; |
||||
} |
||||
} |
||||
|
||||
// 包括boolean的任何类型只有一种匹配情况:name -》 getName
|
||||
return ("get" + handledFieldName).equals(methodName); |
||||
} |
||||
|
||||
/** |
||||
* 方法是否为Setter方法<br> |
||||
* 匹配规则如下(忽略大小写): |
||||
* |
||||
* <pre> |
||||
* 字段名 -》 方法名 |
||||
* isName -》 setName |
||||
* isName -》 setIsName |
||||
* name -》 setName |
||||
* </pre> |
||||
* |
||||
* @param methodName 方法名 |
||||
* @param fieldName 字段名 |
||||
* @param isBooleanField 是否为Boolean类型字段 |
||||
* @param ignoreCase 匹配是否忽略大小写 |
||||
* @return 是否匹配 |
||||
*/ |
||||
private boolean isMatchSetter(String methodName, String fieldName, boolean isBooleanField, boolean ignoreCase) { |
||||
final String handledFieldName; |
||||
if (ignoreCase) { |
||||
// 全部转为小写,忽略大小写比较
|
||||
methodName = methodName.toLowerCase(); |
||||
handledFieldName = fieldName.toLowerCase(); |
||||
fieldName = handledFieldName; |
||||
} else { |
||||
handledFieldName = StrUtil.upperFirst(fieldName); |
||||
} |
||||
|
||||
// 非标准Setter方法跳过
|
||||
if (!methodName.startsWith("set")) { |
||||
return false; |
||||
} |
||||
|
||||
// 针对Boolean类型特殊检查
|
||||
if (isBooleanField && fieldName.startsWith("is")) { |
||||
// 字段是is开头
|
||||
if (("set" + StrUtil.removePrefix(fieldName, "is")).equals(methodName)// isName -》 setName
|
||||
|| ("set" + handledFieldName).equals(methodName)// isName -》 setIsName
|
||||
) { |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
// 包括boolean的任何类型只有一种匹配情况:name -》 setName
|
||||
return ("set" + handledFieldName).equals(methodName); |
||||
} |
||||
// ------------------------------------------------------------------------------------------------------ Private method end
|
||||
} |
@ -0,0 +1,37 @@ |
||||
package cn.hutool.core.bean; |
||||
|
||||
import cn.hutool.core.lang.func.Func0; |
||||
import cn.hutool.core.map.WeakConcurrentMap; |
||||
|
||||
/** |
||||
* Bean属性缓存<br> |
||||
* 缓存用于防止多次反射造成的性能问题 |
||||
* |
||||
* @author Looly |
||||
*/ |
||||
public enum BeanDescCache { |
||||
INSTANCE; |
||||
|
||||
private final WeakConcurrentMap<Class<?>, BeanDesc> bdCache = new WeakConcurrentMap<>(); |
||||
|
||||
/** |
||||
* 获得属性名和{@link BeanDesc}Map映射 |
||||
* |
||||
* @param beanClass Bean的类 |
||||
* @param supplier 对象不存在时创建对象的函数 |
||||
* @return 属性名和{@link BeanDesc}映射 |
||||
* @since 5.4.2 |
||||
*/ |
||||
public BeanDesc getBeanDesc(Class<?> beanClass, Func0<BeanDesc> supplier) { |
||||
return bdCache.computeIfAbsent(beanClass, (key)->supplier.callWithRuntimeException()); |
||||
} |
||||
|
||||
/** |
||||
* 清空全局的Bean属性缓存 |
||||
* |
||||
* @since 5.7.21 |
||||
*/ |
||||
public void clear() { |
||||
this.bdCache.clear(); |
||||
} |
||||
} |
@ -0,0 +1,32 @@ |
||||
package cn.hutool.core.bean; |
||||
|
||||
import cn.hutool.core.exceptions.ExceptionUtil; |
||||
import cn.hutool.core.util.StrUtil; |
||||
|
||||
/** |
||||
* Bean异常 |
||||
* @author xiaoleilu |
||||
*/ |
||||
public class BeanException extends RuntimeException{ |
||||
private static final long serialVersionUID = -8096998667745023423L; |
||||
|
||||
public BeanException(Throwable e) { |
||||
super(ExceptionUtil.getMessage(e), e); |
||||
} |
||||
|
||||
public BeanException(String message) { |
||||
super(message); |
||||
} |
||||
|
||||
public BeanException(String messageTemplate, Object... params) { |
||||
super(StrUtil.format(messageTemplate, params)); |
||||
} |
||||
|
||||
public BeanException(String message, Throwable throwable) { |
||||
super(message, throwable); |
||||
} |
||||
|
||||
public BeanException(Throwable throwable, String messageTemplate, Object... params) { |
||||
super(StrUtil.format(messageTemplate, params), throwable); |
||||
} |
||||
} |
@ -0,0 +1,324 @@ |
||||
package cn.hutool.core.bean; |
||||
|
||||
import cn.hutool.core.collection.CollUtil; |
||||
import cn.hutool.core.collection.ListUtil; |
||||
import cn.hutool.core.convert.Convert; |
||||
import cn.hutool.core.map.MapUtil; |
||||
import cn.hutool.core.util.ArrayUtil; |
||||
import cn.hutool.core.util.CharUtil; |
||||
import cn.hutool.core.util.NumberUtil; |
||||
import cn.hutool.core.util.StrUtil; |
||||
|
||||
import java.io.Serializable; |
||||
import java.util.ArrayList; |
||||
import java.util.Collection; |
||||
import java.util.HashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* Bean路径表达式,用于获取多层嵌套Bean中的字段值或Bean对象<br> |
||||
* 根据给定的表达式,查找Bean中对应的属性值对象。 表达式分为两种: |
||||
* <ol> |
||||
* <li>.表达式,可以获取Bean对象中的属性(字段)值或者Map中key对应的值</li> |
||||
* <li>[]表达式,可以获取集合等对象中对应index的值</li> |
||||
* </ol> |
||||
* <p> |
||||
* 表达式栗子: |
||||
* |
||||
* <pre> |
||||
* persion |
||||
* persion.name |
||||
* persons[3] |
||||
* person.friends[5].name |
||||
* ['person']['friends'][5]['name'] |
||||
* </pre> |
||||
* |
||||
* @author Looly |
||||
* @since 4.0.6 |
||||
*/ |
||||
public class BeanPath implements Serializable { |
||||
private static final long serialVersionUID = 1L; |
||||
|
||||
/** |
||||
* 表达式边界符号数组 |
||||
*/ |
||||
private static final char[] EXP_CHARS = {CharUtil.DOT, CharUtil.BRACKET_START, CharUtil.BRACKET_END}; |
||||
|
||||
private boolean isStartWith = false; |
||||
protected List<String> patternParts; |
||||
|
||||
/** |
||||
* 解析Bean路径表达式为Bean模式<br> |
||||
* Bean表达式,用于获取多层嵌套Bean中的字段值或Bean对象<br> |
||||
* 根据给定的表达式,查找Bean中对应的属性值对象。 表达式分为两种: |
||||
* <ol> |
||||
* <li>.表达式,可以获取Bean对象中的属性(字段)值或者Map中key对应的值</li> |
||||
* <li>[]表达式,可以获取集合等对象中对应index的值</li> |
||||
* </ol> |
||||
* <p> |
||||
* 表达式栗子: |
||||
* |
||||
* <pre> |
||||
* persion |
||||
* persion.name |
||||
* persons[3] |
||||
* person.friends[5].name |
||||
* ['person']['friends'][5]['name'] |
||||
* </pre> |
||||
* |
||||
* @param expression 表达式 |
||||
* @return BeanPath |
||||
*/ |
||||
public static BeanPath create(final String expression) { |
||||
return new BeanPath(expression); |
||||
} |
||||
|
||||
/** |
||||
* 构造 |
||||
* |
||||
* @param expression 表达式 |
||||
*/ |
||||
public BeanPath(final String expression) { |
||||
init(expression); |
||||
} |
||||
|
||||
/** |
||||
* 获取表达式解析后的分段列表 |
||||
* |
||||
* @return 表达式分段列表 |
||||
*/ |
||||
public List<String> getPatternParts() { |
||||
return this.patternParts; |
||||
} |
||||
|
||||
/** |
||||
* 获取Bean中对应表达式的值 |
||||
* |
||||
* @param bean Bean对象或Map或List等 |
||||
* @return 值,如果对应值不存在,则返回null |
||||
*/ |
||||
public Object get(final Object bean) { |
||||
return get(this.patternParts, bean, false); |
||||
} |
||||
|
||||
/** |
||||
* 设置表达式指定位置(或filed对应)的值<br> |
||||
* 若表达式指向一个List则设置其坐标对应位置的值,若指向Map则put对应key的值,Bean则设置字段的值<br> |
||||
* 注意: |
||||
* |
||||
* <pre> |
||||
* 1. 如果为List,如果下标不大于List长度,则替换原有值,否则追加值 |
||||
* 2. 如果为数组,如果下标不大于数组长度,则替换原有值,否则追加值 |
||||
* </pre> |
||||
* |
||||
* @param bean Bean、Map或List |
||||
* @param value 值 |
||||
*/ |
||||
public void set(final Object bean, final Object value) { |
||||
set(bean, this.patternParts, lastIsNumber(this.patternParts), value); |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return this.patternParts.toString(); |
||||
} |
||||
|
||||
//region Private Methods
|
||||
|
||||
/** |
||||
* 设置表达式指定位置(或filed对应)的值<br> |
||||
* 若表达式指向一个List则设置其坐标对应位置的值,若指向Map则put对应key的值,Bean则设置字段的值<br> |
||||
* 注意: |
||||
* |
||||
* <pre> |
||||
* 1. 如果为List,如果下标不大于List长度,则替换原有值,否则追加值 |
||||
* 2. 如果为数组,如果下标不大于数组长度,则替换原有值,否则追加值 |
||||
* </pre> |
||||
* |
||||
* @param bean Bean、Map或List |
||||
* @param patternParts 表达式块列表 |
||||
* @param value 值 |
||||
* @return 值 |
||||
*/ |
||||
private void set(Object bean, List<String> patternParts, boolean nextNumberPart, Object value) { |
||||
Object subBean = this.get(patternParts, bean, true); |
||||
if (null == subBean) { |
||||
final List<String> parentParts = getParentParts(patternParts); |
||||
this.set(bean, parentParts, lastIsNumber(parentParts), nextNumberPart ? new ArrayList<>() : new HashMap<>()); |
||||
//set中有可能做过转换,因此此处重新获取bean
|
||||
subBean = this.get(patternParts, bean, true); |
||||
} |
||||
BeanUtil.setFieldValue(subBean, patternParts.get(patternParts.size() - 1), value); |
||||
} |
||||
|
||||
/** |
||||
* 判断path列表中末尾的标记是否为数字 |
||||
* |
||||
* @param patternParts path列表 |
||||
* @return 是否为数字 |
||||
*/ |
||||
private static boolean lastIsNumber(List<String> patternParts) { |
||||
return NumberUtil.isInteger(patternParts.get(patternParts.size() - 1)); |
||||
} |
||||
|
||||
/** |
||||
* 获取父级路径列表 |
||||
* |
||||
* @param patternParts 路径列表 |
||||
* @return 父级路径列表 |
||||
*/ |
||||
private static List<String> getParentParts(List<String> patternParts) { |
||||
return patternParts.subList(0, patternParts.size() - 1); |
||||
} |
||||
|
||||
/** |
||||
* 获取Bean中对应表达式的值 |
||||
* |
||||
* @param patternParts 表达式分段列表 |
||||
* @param bean Bean对象或Map或List等 |
||||
* @param ignoreLast 是否忽略最后一个值,忽略最后一个值则用于set,否则用于read |
||||
* @return 值,如果对应值不存在,则返回null |
||||
*/ |
||||
private Object get(final List<String> patternParts, final Object bean, final boolean ignoreLast) { |
||||
int length = patternParts.size(); |
||||
if (ignoreLast) { |
||||
length--; |
||||
} |
||||
Object subBean = bean; |
||||
boolean isFirst = true; |
||||
String patternPart; |
||||
for (int i = 0; i < length; i++) { |
||||
patternPart = patternParts.get(i); |
||||
subBean = getFieldValue(subBean, patternPart); |
||||
if (null == subBean) { |
||||
// 支持表达式的第一个对象为Bean本身(若用户定义表达式$开头,则不做此操作)
|
||||
if (isFirst && !this.isStartWith && BeanUtil.isMatchName(bean, patternPart, true)) { |
||||
subBean = bean; |
||||
isFirst = false; |
||||
} else { |
||||
return null; |
||||
} |
||||
} |
||||
} |
||||
return subBean; |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
private static Object getFieldValue(final Object bean, final String expression) { |
||||
if (StrUtil.isBlank(expression)) { |
||||
return null; |
||||
} |
||||
|
||||
if (StrUtil.contains(expression, ':')) { |
||||
// [start:end:step] 模式
|
||||
final List<String> parts = StrUtil.splitTrim(expression, ':'); |
||||
final int start = Integer.parseInt(parts.get(0)); |
||||
final int end = Integer.parseInt(parts.get(1)); |
||||
int step = 1; |
||||
if (3 == parts.size()) { |
||||
step = Integer.parseInt(parts.get(2)); |
||||
} |
||||
if (bean instanceof Collection) { |
||||
return CollUtil.sub((Collection<?>) bean, start, end, step); |
||||
} else if (ArrayUtil.isArray(bean)) { |
||||
return ArrayUtil.sub(bean, start, end, step); |
||||
} |
||||
} else if (StrUtil.contains(expression, ',')) { |
||||
// [num0,num1,num2...]模式或者['key0','key1']模式
|
||||
final List<String> keys = StrUtil.splitTrim(expression, ','); |
||||
if (bean instanceof Collection) { |
||||
return CollUtil.getAny((Collection<?>) bean, Convert.convert(int[].class, keys)); |
||||
} else if (ArrayUtil.isArray(bean)) { |
||||
return ArrayUtil.getAny(bean, Convert.convert(int[].class, keys)); |
||||
} else { |
||||
final String[] unWrappedKeys = new String[keys.size()]; |
||||
for (int i = 0; i < unWrappedKeys.length; i++) { |
||||
unWrappedKeys[i] = StrUtil.unWrap(keys.get(i), '\''); |
||||
} |
||||
if (bean instanceof Map) { |
||||
// 只支持String为key的Map
|
||||
return MapUtil.getAny((Map<String, ?>) bean, unWrappedKeys); |
||||
} else { |
||||
final Map<String, Object> map = BeanUtil.beanToMap(bean); |
||||
return MapUtil.getAny(map, unWrappedKeys); |
||||
} |
||||
} |
||||
} else { |
||||
// 数字或普通字符串
|
||||
return BeanUtil.getFieldValue(bean, expression); |
||||
} |
||||
|
||||
return null; |
||||
} |
||||
|
||||
/** |
||||
* 初始化 |
||||
* |
||||
* @param expression 表达式 |
||||
*/ |
||||
private void init(final String expression) { |
||||
final List<String> localPatternParts = new ArrayList<>(); |
||||
final int length = expression.length(); |
||||
|
||||
final StringBuilder builder = new StringBuilder(); |
||||
char c; |
||||
boolean isNumStart = false;// 下标标识符开始
|
||||
boolean isInWrap = false; //标识是否在引号内
|
||||
for (int i = 0; i < length; i++) { |
||||
c = expression.charAt(i); |
||||
if (0 == i && '$' == c) { |
||||
// 忽略开头的$符,表示当前对象
|
||||
isStartWith = true; |
||||
continue; |
||||
} |
||||
|
||||
if ('\'' == c) { |
||||
// 结束
|
||||
isInWrap = (!isInWrap); |
||||
continue; |
||||
} |
||||
|
||||
if (!isInWrap && ArrayUtil.contains(EXP_CHARS, c)) { |
||||
// 处理边界符号
|
||||
if (CharUtil.BRACKET_END == c) { |
||||
// 中括号(数字下标)结束
|
||||
if (!isNumStart) { |
||||
throw new IllegalArgumentException(StrUtil.format("Bad expression '{}':{}, we find ']' but no '[' !", expression, i)); |
||||
} |
||||
isNumStart = false; |
||||
// 中括号结束加入下标
|
||||
} else { |
||||
if (isNumStart) { |
||||
// 非结束中括号情况下发现起始中括号报错(中括号未关闭)
|
||||
throw new IllegalArgumentException(StrUtil.format("Bad expression '{}':{}, we find '[' but no ']' !", expression, i)); |
||||
} else if (CharUtil.BRACKET_START == c) { |
||||
// 数字下标开始
|
||||
isNumStart = true; |
||||
} |
||||
// 每一个边界符之前的表达式是一个完整的KEY,开始处理KEY
|
||||
} |
||||
if (builder.length() > 0) { |
||||
localPatternParts.add(builder.toString()); |
||||
} |
||||
builder.setLength(0); |
||||
} else { |
||||
// 非边界符号,追加字符
|
||||
builder.append(c); |
||||
} |
||||
} |
||||
|
||||
// 末尾边界符检查
|
||||
if (isNumStart) { |
||||
throw new IllegalArgumentException(StrUtil.format("Bad expression '{}':{}, we find '[' but no ']' !", expression, length - 1)); |
||||
} else { |
||||
if (builder.length() > 0) { |
||||
localPatternParts.add(builder.toString()); |
||||
} |
||||
} |
||||
|
||||
// 不可变List
|
||||
this.patternParts = ListUtil.unmodifiable(localPatternParts); |
||||
} |
||||
//endregion
|
||||
} |
@ -0,0 +1,917 @@ |
||||
package cn.hutool.core.bean; |
||||
|
||||
import cn.hutool.core.bean.copier.BeanCopier; |
||||
import cn.hutool.core.bean.copier.CopyOptions; |
||||
import cn.hutool.core.bean.copier.ValueProvider; |
||||
import cn.hutool.core.collection.CollUtil; |
||||
import cn.hutool.core.collection.ListUtil; |
||||
import cn.hutool.core.convert.Convert; |
||||
import cn.hutool.core.lang.Editor; |
||||
import cn.hutool.core.map.MapUtil; |
||||
import cn.hutool.core.util.ArrayUtil; |
||||
import cn.hutool.core.util.ClassUtil; |
||||
import cn.hutool.core.util.ModifierUtil; |
||||
import cn.hutool.core.util.ObjectUtil; |
||||
import cn.hutool.core.util.ReflectUtil; |
||||
import cn.hutool.core.util.StrUtil; |
||||
|
||||
import java.lang.reflect.Field; |
||||
import java.lang.reflect.Method; |
||||
import java.util.ArrayList; |
||||
import java.util.Arrays; |
||||
import java.util.Collection; |
||||
import java.util.LinkedHashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
import java.util.function.Consumer; |
||||
import java.util.function.Supplier; |
||||
import java.util.stream.Collectors; |
||||
|
||||
/** |
||||
* Bean工具类 |
||||
* |
||||
* <p> |
||||
* 把一个拥有对属性进行set和get方法的类,我们就可以称之为JavaBean。 |
||||
* </p> |
||||
* |
||||
* @author Looly |
||||
* @since 3.1.2 |
||||
*/ |
||||
public class BeanUtil { |
||||
|
||||
/** |
||||
* 判断是否为可读的Bean对象,判定方法是: |
||||
* |
||||
* <pre> |
||||
* 1、是否存在只有无参数的getXXX方法或者isXXX方法 |
||||
* 2、是否存在public类型的字段 |
||||
* </pre> |
||||
* |
||||
* @param clazz 待测试类 |
||||
* @return 是否为可读的Bean对象 |
||||
* @see #hasGetter(Class) |
||||
* @see #hasPublicField(Class) |
||||
*/ |
||||
public static boolean isReadableBean(Class<?> clazz) { |
||||
return hasGetter(clazz) || hasPublicField(clazz); |
||||
} |
||||
|
||||
/** |
||||
* 判断是否为Bean对象,判定方法是: |
||||
* |
||||
* <pre> |
||||
* 1、是否存在只有一个参数的setXXX方法 |
||||
* 2、是否存在public类型的字段 |
||||
* </pre> |
||||
* |
||||
* @param clazz 待测试类 |
||||
* @return 是否为Bean对象 |
||||
* @see #hasSetter(Class) |
||||
* @see #hasPublicField(Class) |
||||
*/ |
||||
public static boolean isBean(Class<?> clazz) { |
||||
return hasSetter(clazz) || hasPublicField(clazz); |
||||
} |
||||
|
||||
/** |
||||
* 判断是否有Setter方法<br> |
||||
* 判定方法是否存在只有一个参数的setXXX方法 |
||||
* |
||||
* @param clazz 待测试类 |
||||
* @return 是否为Bean对象 |
||||
* @since 4.2.2 |
||||
*/ |
||||
public static boolean hasSetter(Class<?> clazz) { |
||||
if (ClassUtil.isNormalClass(clazz)) { |
||||
for (Method method : clazz.getMethods()) { |
||||
if (method.getParameterCount() == 1 && method.getName().startsWith("set")) { |
||||
// 检测包含标准的setXXX方法即视为标准的JavaBean
|
||||
return true; |
||||
} |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
/** |
||||
* 判断是否为Bean对象<br> |
||||
* 判定方法是否存在只有无参数的getXXX方法或者isXXX方法 |
||||
* |
||||
* @param clazz 待测试类 |
||||
* @return 是否为Bean对象 |
||||
* @since 4.2.2 |
||||
*/ |
||||
public static boolean hasGetter(Class<?> clazz) { |
||||
if (ClassUtil.isNormalClass(clazz)) { |
||||
for (Method method : clazz.getMethods()) { |
||||
if (method.getParameterCount() == 0) { |
||||
if (method.getName().startsWith("get") || method.getName().startsWith("is")) { |
||||
return true; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
/** |
||||
* 指定类中是否有public类型字段(static字段除外) |
||||
* |
||||
* @param clazz 待测试类 |
||||
* @return 是否有public类型字段 |
||||
* @since 5.1.0 |
||||
*/ |
||||
public static boolean hasPublicField(Class<?> clazz) { |
||||
if (ClassUtil.isNormalClass(clazz)) { |
||||
for (Field field : clazz.getFields()) { |
||||
if (ModifierUtil.isPublic(field) && !ModifierUtil.isStatic(field)) { |
||||
//非static的public字段
|
||||
return true; |
||||
} |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
/** |
||||
* 创建动态Bean |
||||
* |
||||
* @param bean 普通Bean或Map |
||||
* @return {@link DynaBean} |
||||
* @since 3.0.7 |
||||
*/ |
||||
public static DynaBean createDynaBean(Object bean) { |
||||
return new DynaBean(bean); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 获取{@link BeanDesc} Bean描述信息 |
||||
* |
||||
* @param clazz Bean类 |
||||
* @return {@link BeanDesc} |
||||
* @since 3.1.2 |
||||
*/ |
||||
public static BeanDesc getBeanDesc(Class<?> clazz) { |
||||
return BeanDescCache.INSTANCE.getBeanDesc(clazz, () -> new BeanDesc(clazz)); |
||||
} |
||||
|
||||
/** |
||||
* 遍历Bean的属性 |
||||
* |
||||
* @param clazz Bean类 |
||||
* @param action 每个元素的处理类 |
||||
* @since 5.4.2 |
||||
*/ |
||||
public static void descForEach(Class<?> clazz, Consumer<? super PropDesc> action) { |
||||
getBeanDesc(clazz).getProps().forEach(action); |
||||
} |
||||
|
||||
// --------------------------------------------------------------------------------------------------------- PropertyDescriptor
|
||||
|
||||
|
||||
/** |
||||
* 获得字段值,通过反射直接获得字段值,并不调用getXXX方法<br> |
||||
* 对象同样支持Map类型,fieldNameOrIndex即为key |
||||
* |
||||
* <ul> |
||||
* <li>Map: fieldNameOrIndex需为key,获取对应value</li> |
||||
* <li>Collection: fieldNameOrIndex当为数字,返回index对应值,非数字遍历集合返回子bean对应name值</li> |
||||
* <li>Array: fieldNameOrIndex当为数字,返回index对应值,非数字遍历数组返回子bean对应name值</li> |
||||
* </ul> |
||||
* |
||||
* @param bean Bean对象 |
||||
* @param fieldNameOrIndex 字段名或序号,序号支持负数 |
||||
* @return 字段值 |
||||
*/ |
||||
public static Object getFieldValue(Object bean, String fieldNameOrIndex) { |
||||
if (null == bean || null == fieldNameOrIndex) { |
||||
return null; |
||||
} |
||||
|
||||
if (bean instanceof Map) { |
||||
return ((Map<?, ?>) bean).get(fieldNameOrIndex); |
||||
} else if (bean instanceof Collection) { |
||||
try { |
||||
return CollUtil.get((Collection<?>) bean, Integer.parseInt(fieldNameOrIndex)); |
||||
} catch (NumberFormatException e) { |
||||
// 非数字,see pr#254@Gitee
|
||||
return CollUtil.map((Collection<?>) bean, (beanEle) -> getFieldValue(beanEle, fieldNameOrIndex), false); |
||||
} |
||||
} else if (ArrayUtil.isArray(bean)) { |
||||
try { |
||||
return ArrayUtil.get(bean, Integer.parseInt(fieldNameOrIndex)); |
||||
} catch (NumberFormatException e) { |
||||
// 非数字,see pr#254@Gitee
|
||||
return ArrayUtil.map(bean, Object.class, (beanEle) -> getFieldValue(beanEle, fieldNameOrIndex)); |
||||
} |
||||
} else {// 普通Bean对象
|
||||
return ReflectUtil.getFieldValue(bean, fieldNameOrIndex); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 设置字段值,通过反射设置字段值,并不调用setXXX方法<br> |
||||
* 对象同样支持Map类型,fieldNameOrIndex即为key |
||||
* |
||||
* @param bean Bean |
||||
* @param fieldNameOrIndex 字段名或序号,序号支持负数 |
||||
* @param value 值 |
||||
*/ |
||||
@SuppressWarnings({"unchecked", "rawtypes"}) |
||||
public static void setFieldValue(Object bean, String fieldNameOrIndex, Object value) { |
||||
if (bean instanceof Map) { |
||||
((Map) bean).put(fieldNameOrIndex, value); |
||||
} else if (bean instanceof List) { |
||||
ListUtil.setOrPadding((List) bean, Convert.toInt(fieldNameOrIndex), value); |
||||
} else if (ArrayUtil.isArray(bean)) { |
||||
ArrayUtil.setOrAppend(bean, Convert.toInt(fieldNameOrIndex), value); |
||||
} else { |
||||
// 普通Bean对象
|
||||
ReflectUtil.setFieldValue(bean, fieldNameOrIndex, value); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 解析Bean中的属性值 |
||||
* |
||||
* @param <T> 属性值类型 |
||||
* @param bean Bean对象,支持Map、List、Collection、Array |
||||
* @param expression 表达式,例如:person.friend[5].name |
||||
* @return Bean属性值,bean为{@code null}或者express为空,返回{@code null} |
||||
* @see BeanPath#get(Object) |
||||
* @since 3.0.7 |
||||
*/ |
||||
@SuppressWarnings("unchecked") |
||||
public static <T> T getProperty(Object bean, String expression) { |
||||
if (null == bean || StrUtil.isBlank(expression)) { |
||||
return null; |
||||
} |
||||
return (T) BeanPath.create(expression).get(bean); |
||||
} |
||||
|
||||
/** |
||||
* 解析Bean中的属性值 |
||||
* |
||||
* @param bean Bean对象,支持Map、List、Collection、Array |
||||
* @param expression 表达式,例如:person.friend[5].name |
||||
* @param value 属性值 |
||||
* @see BeanPath#get(Object) |
||||
* @since 4.0.6 |
||||
*/ |
||||
public static void setProperty(Object bean, String expression, Object value) { |
||||
BeanPath.create(expression).set(bean, value); |
||||
} |
||||
|
||||
// --------------------------------------------------------------------------------------------- mapToBean
|
||||
|
||||
/** |
||||
* Map转换为Bean对象 |
||||
* |
||||
* @param <T> Bean类型 |
||||
* @param map {@link Map} |
||||
* @param beanClass Bean Class |
||||
* @param isIgnoreError 是否忽略注入错误 |
||||
* @return Bean |
||||
* @deprecated 请使用 {@link #toBean(Object, Class)} 或 {@link #toBeanIgnoreError(Object, Class)} |
||||
*/ |
||||
@Deprecated |
||||
public static <T> T mapToBean(Map<?, ?> map, Class<T> beanClass, boolean isIgnoreError) { |
||||
return fillBeanWithMap(map, ReflectUtil.newInstanceIfPossible(beanClass), isIgnoreError); |
||||
} |
||||
|
||||
/** |
||||
* Map转换为Bean对象<br> |
||||
* 忽略大小写 |
||||
* |
||||
* @param <T> Bean类型 |
||||
* @param map Map |
||||
* @param beanClass Bean Class |
||||
* @param isIgnoreError 是否忽略注入错误 |
||||
* @return Bean |
||||
* @deprecated 请使用 {@link #toBeanIgnoreCase(Object, Class, boolean)} |
||||
*/ |
||||
@Deprecated |
||||
public static <T> T mapToBeanIgnoreCase(Map<?, ?> map, Class<T> beanClass, boolean isIgnoreError) { |
||||
return fillBeanWithMapIgnoreCase(map, ReflectUtil.newInstanceIfPossible(beanClass), isIgnoreError); |
||||
} |
||||
|
||||
/** |
||||
* Map转换为Bean对象 |
||||
* |
||||
* @param <T> Bean类型 |
||||
* @param map {@link Map} |
||||
* @param beanClass Bean Class |
||||
* @param copyOptions 转Bean选项 |
||||
* @return Bean |
||||
* @deprecated 请使用 {@link #toBean(Object, Class, CopyOptions)} |
||||
*/ |
||||
@Deprecated |
||||
public static <T> T mapToBean(Map<?, ?> map, Class<T> beanClass, CopyOptions copyOptions) { |
||||
return fillBeanWithMap(map, ReflectUtil.newInstanceIfPossible(beanClass), copyOptions); |
||||
} |
||||
|
||||
/** |
||||
* Map转换为Bean对象 |
||||
* |
||||
* @param <T> Bean类型 |
||||
* @param map {@link Map} |
||||
* @param beanClass Bean Class |
||||
* @param isToCamelCase 是否将Map中的下划线风格key转换为驼峰风格 |
||||
* @param copyOptions 转Bean选项 |
||||
* @return Bean |
||||
*/ |
||||
public static <T> T mapToBean(Map<?, ?> map, Class<T> beanClass, boolean isToCamelCase, CopyOptions copyOptions) { |
||||
return fillBeanWithMap(map, ReflectUtil.newInstanceIfPossible(beanClass), isToCamelCase, copyOptions); |
||||
} |
||||
|
||||
// --------------------------------------------------------------------------------------------- fillBeanWithMap
|
||||
|
||||
/** |
||||
* 使用Map填充Bean对象 |
||||
* |
||||
* @param <T> Bean类型 |
||||
* @param map Map |
||||
* @param bean Bean |
||||
* @param isIgnoreError 是否忽略注入错误 |
||||
* @return Bean |
||||
*/ |
||||
public static <T> T fillBeanWithMap(Map<?, ?> map, T bean, boolean isIgnoreError) { |
||||
return fillBeanWithMap(map, bean, false, isIgnoreError); |
||||
} |
||||
|
||||
/** |
||||
* 使用Map填充Bean对象,可配置将下划线转换为驼峰 |
||||
* |
||||
* @param <T> Bean类型 |
||||
* @param map Map |
||||
* @param bean Bean |
||||
* @param isToCamelCase 是否将下划线模式转换为驼峰模式 |
||||
* @param isIgnoreError 是否忽略注入错误 |
||||
* @return Bean |
||||
*/ |
||||
public static <T> T fillBeanWithMap(Map<?, ?> map, T bean, boolean isToCamelCase, boolean isIgnoreError) { |
||||
return fillBeanWithMap(map, bean, isToCamelCase, CopyOptions.create().setIgnoreError(isIgnoreError)); |
||||
} |
||||
|
||||
/** |
||||
* 使用Map填充Bean对象,忽略大小写 |
||||
* |
||||
* @param <T> Bean类型 |
||||
* @param map Map |
||||
* @param bean Bean |
||||
* @param isIgnoreError 是否忽略注入错误 |
||||
* @return Bean |
||||
*/ |
||||
public static <T> T fillBeanWithMapIgnoreCase(Map<?, ?> map, T bean, boolean isIgnoreError) { |
||||
return fillBeanWithMap(map, bean, CopyOptions.create().setIgnoreCase(true).setIgnoreError(isIgnoreError)); |
||||
} |
||||
|
||||
/** |
||||
* 使用Map填充Bean对象 |
||||
* |
||||
* @param <T> Bean类型 |
||||
* @param map Map |
||||
* @param bean Bean |
||||
* @param copyOptions 属性复制选项 {@link CopyOptions} |
||||
* @return Bean |
||||
*/ |
||||
public static <T> T fillBeanWithMap(Map<?, ?> map, T bean, CopyOptions copyOptions) { |
||||
return fillBeanWithMap(map, bean, false, copyOptions); |
||||
} |
||||
|
||||
/** |
||||
* 使用Map填充Bean对象 |
||||
* |
||||
* @param <T> Bean类型 |
||||
* @param map Map |
||||
* @param bean Bean |
||||
* @param isToCamelCase 是否将Map中的下划线风格key转换为驼峰风格 |
||||
* @param copyOptions 属性复制选项 {@link CopyOptions} |
||||
* @return Bean |
||||
* @since 3.3.1 |
||||
*/ |
||||
public static <T> T fillBeanWithMap(Map<?, ?> map, T bean, boolean isToCamelCase, CopyOptions copyOptions) { |
||||
if (MapUtil.isEmpty(map)) { |
||||
return bean; |
||||
} |
||||
if (isToCamelCase) { |
||||
map = MapUtil.toCamelCaseMap(map); |
||||
} |
||||
copyProperties(map, bean, copyOptions); |
||||
return bean; |
||||
} |
||||
|
||||
// --------------------------------------------------------------------------------------------- fillBean
|
||||
|
||||
/** |
||||
* 对象或Map转Bean |
||||
* |
||||
* @param <T> 转换的Bean类型 |
||||
* @param source Bean对象或Map |
||||
* @param clazz 目标的Bean类型 |
||||
* @return Bean对象 |
||||
* @since 4.1.20 |
||||
*/ |
||||
public static <T> T toBean(Object source, Class<T> clazz) { |
||||
return toBean(source, clazz, null); |
||||
} |
||||
|
||||
/** |
||||
* 对象或Map转Bean,忽略字段转换时发生的异常 |
||||
* |
||||
* @param <T> 转换的Bean类型 |
||||
* @param source Bean对象或Map |
||||
* @param clazz 目标的Bean类型 |
||||
* @return Bean对象 |
||||
* @since 5.4.0 |
||||
*/ |
||||
public static <T> T toBeanIgnoreError(Object source, Class<T> clazz) { |
||||
return toBean(source, clazz, CopyOptions.create().setIgnoreError(true)); |
||||
} |
||||
|
||||
/** |
||||
* 对象或Map转Bean,忽略字段转换时发生的异常 |
||||
* |
||||
* @param <T> 转换的Bean类型 |
||||
* @param source Bean对象或Map |
||||
* @param clazz 目标的Bean类型 |
||||
* @param ignoreError 是否忽略注入错误 |
||||
* @return Bean对象 |
||||
* @since 5.4.0 |
||||
*/ |
||||
public static <T> T toBeanIgnoreCase(Object source, Class<T> clazz, boolean ignoreError) { |
||||
return toBean(source, clazz, |
||||
CopyOptions.create() |
||||
.setIgnoreCase(true) |
||||
.setIgnoreError(ignoreError)); |
||||
} |
||||
|
||||
/** |
||||
* 对象或Map转Bean |
||||
* |
||||
* @param <T> 转换的Bean类型 |
||||
* @param source Bean对象或Map |
||||
* @param clazz 目标的Bean类型 |
||||
* @param options 属性拷贝选项 |
||||
* @return Bean对象 |
||||
* @since 5.2.4 |
||||
*/ |
||||
public static <T> T toBean(Object source, Class<T> clazz, CopyOptions options) { |
||||
return toBean(source, () -> ReflectUtil.newInstanceIfPossible(clazz), options); |
||||
} |
||||
|
||||
/** |
||||
* 对象或Map转Bean |
||||
* |
||||
* @param <T> 转换的Bean类型 |
||||
* @param source Bean对象或Map |
||||
* @param targetSupplier 目标的Bean创建器 |
||||
* @param options 属性拷贝选项 |
||||
* @return Bean对象 |
||||
* @since 5.8.0 |
||||
*/ |
||||
public static <T> T toBean(Object source, Supplier<T> targetSupplier, CopyOptions options) { |
||||
if (null == source || null == targetSupplier) { |
||||
return null; |
||||
} |
||||
final T target = targetSupplier.get(); |
||||
copyProperties(source, target, options); |
||||
return target; |
||||
} |
||||
|
||||
/** |
||||
* ServletRequest 参数转Bean |
||||
* |
||||
* @param <T> Bean类型 |
||||
* @param beanClass Bean Class |
||||
* @param valueProvider 值提供者 |
||||
* @param copyOptions 拷贝选项,见 {@link CopyOptions} |
||||
* @return Bean |
||||
*/ |
||||
public static <T> T toBean(Class<T> beanClass, ValueProvider<String> valueProvider, CopyOptions copyOptions) { |
||||
if (null == beanClass || null == valueProvider) { |
||||
return null; |
||||
} |
||||
return fillBean(ReflectUtil.newInstanceIfPossible(beanClass), valueProvider, copyOptions); |
||||
} |
||||
|
||||
/** |
||||
* 填充Bean的核心方法 |
||||
* |
||||
* @param <T> Bean类型 |
||||
* @param bean Bean |
||||
* @param valueProvider 值提供者 |
||||
* @param copyOptions 拷贝选项,见 {@link CopyOptions} |
||||
* @return Bean |
||||
*/ |
||||
public static <T> T fillBean(T bean, ValueProvider<String> valueProvider, CopyOptions copyOptions) { |
||||
if (null == valueProvider) { |
||||
return bean; |
||||
} |
||||
|
||||
return BeanCopier.create(valueProvider, bean, copyOptions).copy(); |
||||
} |
||||
|
||||
// --------------------------------------------------------------------------------------------- beanToMap
|
||||
/** |
||||
* 将bean的部分属性转换成map<br> |
||||
* 可选拷贝哪些属性值,默认是不忽略值为{@code null}的值的。 |
||||
* |
||||
* @param bean bean |
||||
* @param properties 需要拷贝的属性值,{@code null}或空表示拷贝所有值 |
||||
* @return Map |
||||
* @since 5.8.0 |
||||
*/ |
||||
public static Map<String, Object> beanToMap(Object bean, String... properties) { |
||||
int mapSize = 16; |
||||
Editor<String> keyEditor = null; |
||||
if(ArrayUtil.isNotEmpty(properties)){ |
||||
mapSize = properties.length; |
||||
final Set<String> propertiesSet = CollUtil.set(false, properties); |
||||
keyEditor = property -> propertiesSet.contains(property) ? property : null; |
||||
} |
||||
|
||||
// 指明了要复制的属性 所以不忽略null值
|
||||
return beanToMap(bean, new LinkedHashMap<>(mapSize, 1), false, keyEditor); |
||||
} |
||||
|
||||
/** |
||||
* 对象转Map |
||||
* |
||||
* @param bean bean对象 |
||||
* @param isToUnderlineCase 是否转换为下划线模式 |
||||
* @param ignoreNullValue 是否忽略值为空的字段 |
||||
* @return Map |
||||
*/ |
||||
public static Map<String, Object> beanToMap(Object bean, boolean isToUnderlineCase, boolean ignoreNullValue) { |
||||
if (null == bean) { |
||||
return null; |
||||
} |
||||
return beanToMap(bean, new LinkedHashMap<>(), isToUnderlineCase, ignoreNullValue); |
||||
} |
||||
|
||||
/** |
||||
* 对象转Map |
||||
* |
||||
* @param bean bean对象 |
||||
* @param targetMap 目标的Map |
||||
* @param isToUnderlineCase 是否转换为下划线模式 |
||||
* @param ignoreNullValue 是否忽略值为空的字段 |
||||
* @return Map |
||||
* @since 3.2.3 |
||||
*/ |
||||
public static Map<String, Object> beanToMap(Object bean, Map<String, Object> targetMap, final boolean isToUnderlineCase, boolean ignoreNullValue) { |
||||
if (null == bean) { |
||||
return null; |
||||
} |
||||
|
||||
return beanToMap(bean, targetMap, ignoreNullValue, key -> isToUnderlineCase ? StrUtil.toUnderlineCase(key) : key); |
||||
} |
||||
|
||||
/** |
||||
* 对象转Map<br> |
||||
* 通过实现{@link Editor} 可以自定义字段值,如果这个Editor返回null则忽略这个字段,以便实现: |
||||
* |
||||
* <pre> |
||||
* 1. 字段筛选,可以去除不需要的字段 |
||||
* 2. 字段变换,例如实现驼峰转下划线 |
||||
* 3. 自定义字段前缀或后缀等等 |
||||
* </pre> |
||||
* |
||||
* @param bean bean对象 |
||||
* @param targetMap 目标的Map |
||||
* @param ignoreNullValue 是否忽略值为空的字段 |
||||
* @param keyEditor 属性字段(Map的key)编辑器,用于筛选、编辑key,如果这个Editor返回null则忽略这个字段 |
||||
* @return Map |
||||
* @since 4.0.5 |
||||
*/ |
||||
public static Map<String, Object> beanToMap(Object bean, Map<String, Object> targetMap, boolean ignoreNullValue, Editor<String> keyEditor) { |
||||
if (null == bean) { |
||||
return null; |
||||
} |
||||
|
||||
return BeanCopier.create(bean, targetMap, |
||||
CopyOptions.create() |
||||
.setIgnoreNullValue(ignoreNullValue) |
||||
.setFieldNameEditor(keyEditor) |
||||
).copy(); |
||||
} |
||||
|
||||
/** |
||||
* 对象转Map<br> |
||||
* 通过自定义{@link CopyOptions} 完成抓换选项,以便实现: |
||||
* |
||||
* <pre> |
||||
* 1. 字段筛选,可以去除不需要的字段 |
||||
* 2. 字段变换,例如实现驼峰转下划线 |
||||
* 3. 自定义字段前缀或后缀等等 |
||||
* 4. 字段值处理 |
||||
* ... |
||||
* </pre> |
||||
* |
||||
* @param bean bean对象 |
||||
* @param targetMap 目标的Map |
||||
* @param copyOptions 拷贝选项 |
||||
* @return Map |
||||
* @since 5.7.15 |
||||
*/ |
||||
public static Map<String, Object> beanToMap(Object bean, Map<String, Object> targetMap, CopyOptions copyOptions) { |
||||
if (null == bean) { |
||||
return null; |
||||
} |
||||
|
||||
return BeanCopier.create(bean, targetMap, copyOptions).copy(); |
||||
} |
||||
|
||||
// --------------------------------------------------------------------------------------------- copyProperties
|
||||
|
||||
/** |
||||
* 按照Bean对象属性创建对应的Class对象,并忽略某些属性 |
||||
* |
||||
* @param <T> 对象类型 |
||||
* @param source 源Bean对象 |
||||
* @param tClass 目标Class |
||||
* @param ignoreProperties 不拷贝的的属性列表 |
||||
* @return 目标对象 |
||||
*/ |
||||
public static <T> T copyProperties(Object source, Class<T> tClass, String... ignoreProperties) { |
||||
if(null == source){ |
||||
return null; |
||||
} |
||||
T target = ReflectUtil.newInstanceIfPossible(tClass); |
||||
copyProperties(source, target, CopyOptions.create().setIgnoreProperties(ignoreProperties)); |
||||
return target; |
||||
} |
||||
|
||||
/** |
||||
* 复制Bean对象属性<br> |
||||
* 限制类用于限制拷贝的属性,例如一个类我只想复制其父类的一些属性,就可以将editable设置为父类 |
||||
* |
||||
* @param source 源Bean对象 |
||||
* @param target 目标Bean对象 |
||||
* @param ignoreProperties 不拷贝的的属性列表 |
||||
*/ |
||||
public static void copyProperties(Object source, Object target, String... ignoreProperties) { |
||||
copyProperties(source, target, CopyOptions.create().setIgnoreProperties(ignoreProperties)); |
||||
} |
||||
|
||||
/** |
||||
* 复制Bean对象属性<br> |
||||
* |
||||
* @param source 源Bean对象 |
||||
* @param target 目标Bean对象 |
||||
* @param ignoreCase 是否忽略大小写 |
||||
*/ |
||||
public static void copyProperties(Object source, Object target, boolean ignoreCase) { |
||||
BeanCopier.create(source, target, CopyOptions.create().setIgnoreCase(ignoreCase)).copy(); |
||||
} |
||||
|
||||
/** |
||||
* 复制Bean对象属性<br> |
||||
* 限制类用于限制拷贝的属性,例如一个类我只想复制其父类的一些属性,就可以将editable设置为父类 |
||||
* |
||||
* @param source 源Bean对象 |
||||
* @param target 目标Bean对象 |
||||
* @param copyOptions 拷贝选项,见 {@link CopyOptions} |
||||
*/ |
||||
public static void copyProperties(Object source, Object target, CopyOptions copyOptions) { |
||||
if(null == source){ |
||||
return; |
||||
} |
||||
BeanCopier.create(source, target, ObjectUtil.defaultIfNull(copyOptions, CopyOptions::create)).copy(); |
||||
} |
||||
|
||||
/** |
||||
* 复制集合中的Bean属性<br> |
||||
* 此方法遍历集合中每个Bean,复制其属性后加入一个新的{@link List}中。 |
||||
* |
||||
* @param collection 原Bean集合 |
||||
* @param targetType 目标Bean类型 |
||||
* @param copyOptions 拷贝选项 |
||||
* @param <T> Bean类型 |
||||
* @return 复制后的List |
||||
* @since 5.6.4 |
||||
*/ |
||||
public static <T> List<T> copyToList(Collection<?> collection, Class<T> targetType, CopyOptions copyOptions) { |
||||
if (null == collection) { |
||||
return null; |
||||
} |
||||
if (collection.isEmpty()) { |
||||
return new ArrayList<>(0); |
||||
} |
||||
return collection.stream().map((source) -> { |
||||
final T target = ReflectUtil.newInstanceIfPossible(targetType); |
||||
copyProperties(source, target, copyOptions); |
||||
return target; |
||||
}).collect(Collectors.toList()); |
||||
} |
||||
|
||||
/** |
||||
* 复制集合中的Bean属性<br> |
||||
* 此方法遍历集合中每个Bean,复制其属性后加入一个新的{@link List}中。 |
||||
* |
||||
* @param collection 原Bean集合 |
||||
* @param targetType 目标Bean类型 |
||||
* @param <T> Bean类型 |
||||
* @return 复制后的List |
||||
* @since 5.6.6 |
||||
*/ |
||||
public static <T> List<T> copyToList(Collection<?> collection, Class<T> targetType) { |
||||
return copyToList(collection, targetType, CopyOptions.create()); |
||||
} |
||||
|
||||
/** |
||||
* 给定的Bean的类名是否匹配指定类名字符串<br> |
||||
* 如果isSimple为{@code true},则只匹配类名而忽略包名,例如:cn.hutool.TestEntity只匹配TestEntity<br> |
||||
* 如果isSimple为{@code false},则匹配包括包名的全类名,例如:cn.hutool.TestEntity匹配cn.hutool.TestEntity |
||||
* |
||||
* @param bean Bean |
||||
* @param beanClassName Bean的类名 |
||||
* @param isSimple 是否只匹配类名而忽略包名,true表示忽略包名 |
||||
* @return 是否匹配 |
||||
* @since 4.0.6 |
||||
*/ |
||||
public static boolean isMatchName(Object bean, String beanClassName, boolean isSimple) { |
||||
if (null == bean || StrUtil.isBlank(beanClassName)) { |
||||
return false; |
||||
} |
||||
return ClassUtil.getClassName(bean, isSimple).equals(isSimple ? StrUtil.upperFirst(beanClassName) : beanClassName); |
||||
} |
||||
|
||||
/** |
||||
* 编辑Bean的字段,static字段不会处理<br> |
||||
* 例如需要对指定的字段做判空操作、null转""操作等等。 |
||||
* |
||||
* @param bean bean |
||||
* @param editor 编辑器函数 |
||||
* @param <T> 被编辑的Bean类型 |
||||
* @return bean |
||||
* @since 5.6.4 |
||||
*/ |
||||
public static <T> T edit(T bean, Editor<Field> editor) { |
||||
if (bean == null) { |
||||
return null; |
||||
} |
||||
|
||||
final Field[] fields = ReflectUtil.getFields(bean.getClass()); |
||||
for (Field field : fields) { |
||||
if (ModifierUtil.isStatic(field)) { |
||||
continue; |
||||
} |
||||
editor.edit(field); |
||||
} |
||||
return bean; |
||||
} |
||||
|
||||
/** |
||||
* 把Bean里面的String属性做trim操作。此方法直接对传入的Bean做修改。 |
||||
* <p> |
||||
* 通常bean直接用来绑定页面的input,用户的输入可能首尾存在空格,通常保存数据库前需要把首尾空格去掉 |
||||
* |
||||
* @param <T> Bean类型 |
||||
* @param bean Bean对象 |
||||
* @param ignoreFields 不需要trim的Field名称列表(不区分大小写) |
||||
* @return 处理后的Bean对象 |
||||
*/ |
||||
public static <T> T trimStrFields(T bean, String... ignoreFields) { |
||||
return edit(bean, (field) -> { |
||||
if (ignoreFields != null && ArrayUtil.containsIgnoreCase(ignoreFields, field.getName())) { |
||||
// 不处理忽略的Fields
|
||||
return field; |
||||
} |
||||
if (String.class.equals(field.getType())) { |
||||
// 只有String的Field才处理
|
||||
final String val = (String) ReflectUtil.getFieldValue(bean, field); |
||||
if (null != val) { |
||||
final String trimVal = StrUtil.trim(val); |
||||
if (!val.equals(trimVal)) { |
||||
// Field Value不为null,且首尾有空格才处理
|
||||
ReflectUtil.setFieldValue(bean, field, trimVal); |
||||
} |
||||
} |
||||
} |
||||
return field; |
||||
}); |
||||
} |
||||
|
||||
/** |
||||
* 判断Bean是否为非空对象,非空对象表示本身不为{@code null}或者含有非{@code null}属性的对象 |
||||
* |
||||
* @param bean Bean对象 |
||||
* @param ignoreFieldNames 忽略检查的字段名 |
||||
* @return 是否为非空,{@code true} - 非空 / {@code false} - 空 |
||||
* @since 5.0.7 |
||||
*/ |
||||
public static boolean isNotEmpty(Object bean, String... ignoreFieldNames) { |
||||
return !isEmpty(bean, ignoreFieldNames); |
||||
} |
||||
|
||||
/** |
||||
* 判断Bean是否为空对象,空对象表示本身为{@code null}或者所有属性都为{@code null}<br> |
||||
* 此方法不判断static属性 |
||||
* |
||||
* @param bean Bean对象 |
||||
* @param ignoreFieldNames 忽略检查的字段名 |
||||
* @return 是否为空,{@code true} - 空 / {@code false} - 非空 |
||||
* @since 4.1.10 |
||||
*/ |
||||
public static boolean isEmpty(Object bean, String... ignoreFieldNames) { |
||||
if (null != bean) { |
||||
for (Field field : ReflectUtil.getFields(bean.getClass())) { |
||||
if (ModifierUtil.isStatic(field)) { |
||||
continue; |
||||
} |
||||
if ((!ArrayUtil.contains(ignoreFieldNames, field.getName())) |
||||
&& null != ReflectUtil.getFieldValue(bean, field)) { |
||||
return false; |
||||
} |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* 判断Bean是否包含值为{@code null}的属性<br> |
||||
* 对象本身为{@code null}也返回true |
||||
* |
||||
* @param bean Bean对象 |
||||
* @param ignoreFieldNames 忽略检查的字段名 |
||||
* @return 是否包含值为<code>null</code>的属性,{@code true} - 包含 / {@code false} - 不包含 |
||||
* @since 4.1.10 |
||||
*/ |
||||
public static boolean hasNullField(Object bean, String... ignoreFieldNames) { |
||||
if (null == bean) { |
||||
return true; |
||||
} |
||||
for (Field field : ReflectUtil.getFields(bean.getClass())) { |
||||
if (ModifierUtil.isStatic(field)) { |
||||
continue; |
||||
} |
||||
if ((!ArrayUtil.contains(ignoreFieldNames, field.getName())) |
||||
&& null == ReflectUtil.getFieldValue(bean, field)) { |
||||
return true; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
/** |
||||
* 获取Getter或Setter方法名对应的字段名称,规则如下: |
||||
* <ul> |
||||
* <li>getXxxx获取为xxxx,如getName得到name。</li> |
||||
* <li>setXxxx获取为xxxx,如setName得到name。</li> |
||||
* <li>isXxxx获取为xxxx,如isName得到name。</li> |
||||
* <li>其它不满足规则的方法名抛出{@link IllegalArgumentException}</li> |
||||
* </ul> |
||||
* |
||||
* @param getterOrSetterName Getter或Setter方法名 |
||||
* @return 字段名称 |
||||
* @throws IllegalArgumentException 非Getter或Setter方法 |
||||
* @since 5.7.23 |
||||
*/ |
||||
public static String getFieldName(String getterOrSetterName) { |
||||
if (getterOrSetterName.startsWith("get") || getterOrSetterName.startsWith("set")) { |
||||
return StrUtil.removePreAndLowerFirst(getterOrSetterName, 3); |
||||
} else if (getterOrSetterName.startsWith("is")) { |
||||
return StrUtil.removePreAndLowerFirst(getterOrSetterName, 2); |
||||
} else { |
||||
throw new IllegalArgumentException("Invalid Getter or Setter name: " + getterOrSetterName); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 判断source与target的所有公共字段的值是否相同 |
||||
* |
||||
* @param source 待检测对象1 |
||||
* @param target 待检测对象2 |
||||
* @param ignoreProperties 不需要检测的字段 |
||||
* @return 判断结果,如果为true则证明所有字段的值都相同 |
||||
* @since 5.8.4 |
||||
* @author Takak11 |
||||
*/ |
||||
public static boolean isCommonFieldsEqual(Object source, Object target, String...ignoreProperties) { |
||||
|
||||
if (null == source && null == target) { |
||||
return true; |
||||
} |
||||
if (null == source || null == target) { |
||||
return false; |
||||
} |
||||
|
||||
Map<String, Object> sourceFieldsMap = BeanUtil.beanToMap(source); |
||||
Map<String, Object> targetFieldsMap = BeanUtil.beanToMap(target); |
||||
|
||||
Set<String> sourceFields = sourceFieldsMap.keySet(); |
||||
sourceFields.removeAll(Arrays.asList(ignoreProperties)); |
||||
|
||||
for (String field : sourceFields) { |
||||
if(ObjectUtil.notEqual(sourceFieldsMap.get(field), targetFieldsMap.get(field))){ |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
} |
@ -0,0 +1,226 @@ |
||||
package cn.hutool.core.bean; |
||||
|
||||
import cn.hutool.core.clone.CloneSupport; |
||||
import cn.hutool.core.lang.Assert; |
||||
import cn.hutool.core.util.ClassUtil; |
||||
import cn.hutool.core.util.ReflectUtil; |
||||
|
||||
import java.io.Serializable; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* 动态Bean,通过反射对Bean的相关方法做操作<br> |
||||
* 支持Map和普通Bean |
||||
* |
||||
* @author Looly |
||||
* @since 3.0.7 |
||||
*/ |
||||
public class DynaBean extends CloneSupport<DynaBean> implements Serializable { |
||||
private static final long serialVersionUID = 1L; |
||||
|
||||
private final Class<?> beanClass; |
||||
private final Object bean; |
||||
|
||||
/** |
||||
* 创建一个DynaBean |
||||
* |
||||
* @param bean 普通Bean |
||||
* @return DynaBean |
||||
*/ |
||||
public static DynaBean create(Object bean) { |
||||
return new DynaBean(bean); |
||||
} |
||||
|
||||
/** |
||||
* 创建一个DynaBean |
||||
* |
||||
* @param beanClass Bean类 |
||||
* @return DynaBean |
||||
*/ |
||||
public static DynaBean create(Class<?> beanClass) { |
||||
return new DynaBean(beanClass); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 创建一个DynaBean |
||||
* |
||||
* @param beanClass Bean类 |
||||
* @param params 构造Bean所需要的参数 |
||||
* @return DynaBean |
||||
*/ |
||||
public static DynaBean create(Class<?> beanClass, Object... params) { |
||||
return new DynaBean(beanClass, params); |
||||
} |
||||
|
||||
//------------------------------------------------------------------------ Constructor start
|
||||
|
||||
/** |
||||
* 构造 |
||||
* |
||||
* @param beanClass Bean类 |
||||
* @param params 构造Bean所需要的参数 |
||||
*/ |
||||
public DynaBean(Class<?> beanClass, Object... params) { |
||||
this(ReflectUtil.newInstance(beanClass, params)); |
||||
} |
||||
|
||||
/** |
||||
* 构造 |
||||
* |
||||
* @param beanClass Bean类 |
||||
*/ |
||||
public DynaBean(Class<?> beanClass) { |
||||
this(ReflectUtil.newInstance(beanClass)); |
||||
} |
||||
|
||||
/** |
||||
* 构造 |
||||
* |
||||
* @param bean 原始Bean |
||||
*/ |
||||
public DynaBean(Object bean) { |
||||
Assert.notNull(bean); |
||||
if (bean instanceof DynaBean) { |
||||
bean = ((DynaBean) bean).getBean(); |
||||
} |
||||
this.bean = bean; |
||||
this.beanClass = ClassUtil.getClass(bean); |
||||
} |
||||
//------------------------------------------------------------------------ Constructor end
|
||||
|
||||
/** |
||||
* 获得字段对应值 |
||||
* |
||||
* @param <T> 属性值类型 |
||||
* @param fieldName 字段名 |
||||
* @return 字段值 |
||||
* @throws BeanException 反射获取属性值或字段值导致的异常 |
||||
*/ |
||||
@SuppressWarnings("unchecked") |
||||
public <T> T get(String fieldName) throws BeanException { |
||||
if (Map.class.isAssignableFrom(beanClass)) { |
||||
return (T) ((Map<?, ?>) bean).get(fieldName); |
||||
} else { |
||||
final PropDesc prop = BeanUtil.getBeanDesc(beanClass).getProp(fieldName); |
||||
if (null == prop) { |
||||
throw new BeanException("No public field or get method for {}", fieldName); |
||||
} |
||||
return (T) prop.getValue(bean); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 检查是否有指定名称的bean属性 |
||||
* |
||||
* @param fieldName 字段名 |
||||
* @return 是否有bean属性 |
||||
* @since 5.4.2 |
||||
*/ |
||||
public boolean containsProp(String fieldName) { |
||||
if (Map.class.isAssignableFrom(beanClass)) { |
||||
return ((Map<?, ?>) bean).containsKey(fieldName); |
||||
} else{ |
||||
return null != BeanUtil.getBeanDesc(beanClass).getProp(fieldName); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 获得字段对应值,获取异常返回{@code null} |
||||
* |
||||
* @param <T> 属性值类型 |
||||
* @param fieldName 字段名 |
||||
* @return 字段值 |
||||
* @since 3.1.1 |
||||
*/ |
||||
public <T> T safeGet(String fieldName) { |
||||
try { |
||||
return get(fieldName); |
||||
} catch (Exception e) { |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 设置字段值 |
||||
* |
||||
* @param fieldName 字段名 |
||||
* @param value 字段值 |
||||
* @throws BeanException 反射获取属性值或字段值导致的异常 |
||||
*/ |
||||
@SuppressWarnings({"unchecked", "rawtypes"}) |
||||
public void set(String fieldName, Object value) throws BeanException { |
||||
if (Map.class.isAssignableFrom(beanClass)) { |
||||
((Map) bean).put(fieldName, value); |
||||
} else { |
||||
final PropDesc prop = BeanUtil.getBeanDesc(beanClass).getProp(fieldName); |
||||
if (null == prop) { |
||||
throw new BeanException("No public field or set method for {}", fieldName); |
||||
} |
||||
prop.setValue(bean, value); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 执行原始Bean中的方法 |
||||
* |
||||
* @param methodName 方法名 |
||||
* @param params 参数 |
||||
* @return 执行结果,可能为null |
||||
*/ |
||||
public Object invoke(String methodName, Object... params) { |
||||
return ReflectUtil.invoke(this.bean, methodName, params); |
||||
} |
||||
|
||||
/** |
||||
* 获得原始Bean |
||||
* |
||||
* @param <T> Bean类型 |
||||
* @return bean |
||||
*/ |
||||
@SuppressWarnings("unchecked") |
||||
public <T> T getBean() { |
||||
return (T) this.bean; |
||||
} |
||||
|
||||
/** |
||||
* 获得Bean的类型 |
||||
* |
||||
* @param <T> Bean类型 |
||||
* @return Bean类型 |
||||
*/ |
||||
@SuppressWarnings("unchecked") |
||||
public <T> Class<T> getBeanClass() { |
||||
return (Class<T>) this.beanClass; |
||||
} |
||||
|
||||
@Override |
||||
public int hashCode() { |
||||
final int prime = 31; |
||||
int result = 1; |
||||
result = prime * result + ((bean == null) ? 0 : bean.hashCode()); |
||||
return result; |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(Object obj) { |
||||
if (this == obj) { |
||||
return true; |
||||
} |
||||
if (obj == null) { |
||||
return false; |
||||
} |
||||
if (getClass() != obj.getClass()) { |
||||
return false; |
||||
} |
||||
final DynaBean other = (DynaBean) obj; |
||||
if (bean == null) { |
||||
return other.bean == null; |
||||
} else return bean.equals(other.bean); |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return this.bean.toString(); |
||||
} |
||||
} |
@ -0,0 +1,29 @@ |
||||
package cn.hutool.core.bean; |
||||
|
||||
/** |
||||
* 为了解决反射过程中,需要传递null参数,但是会丢失参数类型而设立的包装类 |
||||
* |
||||
* @param <T> Null值对应的类型 |
||||
* @author Lillls |
||||
* @since 5.5.0 |
||||
*/ |
||||
public class NullWrapperBean<T> { |
||||
|
||||
private final Class<T> clazz; |
||||
|
||||
/** |
||||
* @param clazz null的类型 |
||||
*/ |
||||
public NullWrapperBean(Class<T> clazz) { |
||||
this.clazz = clazz; |
||||
} |
||||
|
||||
/** |
||||
* 获取null值对应的类型 |
||||
* |
||||
* @return 类型 |
||||
*/ |
||||
public Class<T> getWrappedClass() { |
||||
return clazz; |
||||
} |
||||
} |
@ -0,0 +1,403 @@ |
||||
package cn.hutool.core.bean; |
||||
|
||||
import cn.hutool.core.annotation.AnnotationUtil; |
||||
import cn.hutool.core.annotation.PropIgnore; |
||||
import cn.hutool.core.convert.Convert; |
||||
import cn.hutool.core.util.ClassUtil; |
||||
import cn.hutool.core.util.ModifierUtil; |
||||
import cn.hutool.core.util.ReflectUtil; |
||||
import cn.hutool.core.util.TypeUtil; |
||||
|
||||
import java.lang.reflect.Field; |
||||
import java.lang.reflect.Method; |
||||
import java.lang.reflect.Type; |
||||
|
||||
/** |
||||
* 属性描述,包括了字段、getter、setter和相应的方法执行 |
||||
* |
||||
* @author looly |
||||
*/ |
||||
public class PropDesc { |
||||
|
||||
/** |
||||
* 字段 |
||||
*/ |
||||
final Field field; |
||||
/** |
||||
* Getter方法 |
||||
*/ |
||||
protected Method getter; |
||||
/** |
||||
* Setter方法 |
||||
*/ |
||||
protected Method setter; |
||||
|
||||
/** |
||||
* 构造<br> |
||||
* Getter和Setter方法设置为默认可访问 |
||||
* |
||||
* @param field 字段 |
||||
* @param getter get方法 |
||||
* @param setter set方法 |
||||
*/ |
||||
public PropDesc(Field field, Method getter, Method setter) { |
||||
this.field = field; |
||||
this.getter = ClassUtil.setAccessible(getter); |
||||
this.setter = ClassUtil.setAccessible(setter); |
||||
} |
||||
|
||||
/** |
||||
* 获取字段名,如果存在Alias注解,读取注解的值作为名称 |
||||
* |
||||
* @return 字段名 |
||||
*/ |
||||
public String getFieldName() { |
||||
return ReflectUtil.getFieldName(this.field); |
||||
} |
||||
|
||||
/** |
||||
* 获取字段名称 |
||||
* |
||||
* @return 字段名 |
||||
* @since 5.1.6 |
||||
*/ |
||||
public String getRawFieldName() { |
||||
return null == this.field ? null : this.field.getName(); |
||||
} |
||||
|
||||
/** |
||||
* 获取字段 |
||||
* |
||||
* @return 字段 |
||||
*/ |
||||
public Field getField() { |
||||
return this.field; |
||||
} |
||||
|
||||
/** |
||||
* 获得字段类型<br> |
||||
* 先获取字段的类型,如果字段不存在,则获取Getter方法的返回类型,否则获取Setter的第一个参数类型 |
||||
* |
||||
* @return 字段类型 |
||||
*/ |
||||
public Type getFieldType() { |
||||
if (null != this.field) { |
||||
return TypeUtil.getType(this.field); |
||||
} |
||||
return findPropType(getter, setter); |
||||
} |
||||
|
||||
/** |
||||
* 获得字段类型<br> |
||||
* 先获取字段的类型,如果字段不存在,则获取Getter方法的返回类型,否则获取Setter的第一个参数类型 |
||||
* |
||||
* @return 字段类型 |
||||
*/ |
||||
public Class<?> getFieldClass() { |
||||
if (null != this.field) { |
||||
return TypeUtil.getClass(this.field); |
||||
} |
||||
return findPropClass(getter, setter); |
||||
} |
||||
|
||||
/** |
||||
* 获取Getter方法,可能为{@code null} |
||||
* |
||||
* @return Getter方法 |
||||
*/ |
||||
public Method getGetter() { |
||||
return this.getter; |
||||
} |
||||
|
||||
/** |
||||
* 获取Setter方法,可能为{@code null} |
||||
* |
||||
* @return {@link Method}Setter 方法对象 |
||||
*/ |
||||
public Method getSetter() { |
||||
return this.setter; |
||||
} |
||||
|
||||
/** |
||||
* 检查属性是否可读(即是否可以通过{@link #getValue(Object)}获取到值) |
||||
* |
||||
* @param checkTransient 是否检查Transient关键字或注解 |
||||
* @return 是否可读 |
||||
* @since 5.4.2 |
||||
*/ |
||||
public boolean isReadable(boolean checkTransient) { |
||||
// 检查是否有getter方法或是否为public修饰
|
||||
if (null == this.getter && !ModifierUtil.isPublic(this.field)) { |
||||
return false; |
||||
} |
||||
|
||||
// 检查transient关键字和@Transient注解
|
||||
if (checkTransient && isTransientForGet()) { |
||||
return false; |
||||
} |
||||
|
||||
// 检查@PropIgnore注解
|
||||
return !isIgnoreGet(); |
||||
} |
||||
|
||||
/** |
||||
* 获取属性值<br> |
||||
* 首先调用字段对应的Getter方法获取值,如果Getter方法不存在,则判断字段如果为public,则直接获取字段值<br> |
||||
* 此方法不检查任何注解,使用前需调用 {@link #isReadable(boolean)} 检查是否可读 |
||||
* |
||||
* @param bean Bean对象 |
||||
* @return 字段值 |
||||
* @since 4.0.5 |
||||
*/ |
||||
public Object getValue(Object bean) { |
||||
if (null != this.getter) { |
||||
return ReflectUtil.invoke(bean, this.getter); |
||||
} else if (ModifierUtil.isPublic(this.field)) { |
||||
return ReflectUtil.getFieldValue(bean, this.field); |
||||
} |
||||
|
||||
return null; |
||||
} |
||||
|
||||
/** |
||||
* 获取属性值,自动转换属性值类型<br> |
||||
* 首先调用字段对应的Getter方法获取值,如果Getter方法不存在,则判断字段如果为public,则直接获取字段值 |
||||
* |
||||
* @param bean Bean对象 |
||||
* @param targetType 返回属性值需要转换的类型,null表示不转换 |
||||
* @param ignoreError 是否忽略错误,包括转换错误和注入错误 |
||||
* @return this |
||||
* @since 5.4.2 |
||||
*/ |
||||
public Object getValue(Object bean, Type targetType, boolean ignoreError) { |
||||
Object result = null; |
||||
try { |
||||
result = getValue(bean); |
||||
} catch (Exception e) { |
||||
if (!ignoreError) { |
||||
throw new BeanException(e, "Get value of [{}] error!", getFieldName()); |
||||
} |
||||
} |
||||
|
||||
if (null != result && null != targetType) { |
||||
// 尝试将结果转换为目标类型,如果转换失败,返回null,即跳过此属性值。
|
||||
// 来自:issues#I41WKP@Gitee,当忽略错误情况下,目标类型转换失败应返回null
|
||||
// 如果返回原值,在集合注入时会成功,但是集合取值时会报类型转换错误
|
||||
return Convert.convertWithCheck(targetType, result, null, ignoreError); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* 检查属性是否可读(即是否可以通过{@link #getValue(Object)}获取到值) |
||||
* |
||||
* @param checkTransient 是否检查Transient关键字或注解 |
||||
* @return 是否可读 |
||||
* @since 5.4.2 |
||||
*/ |
||||
public boolean isWritable(boolean checkTransient) { |
||||
// 检查是否有getter方法或是否为public修饰
|
||||
if (null == this.setter && !ModifierUtil.isPublic(this.field)) { |
||||
return false; |
||||
} |
||||
|
||||
// 检查transient关键字和@Transient注解
|
||||
if (checkTransient && isTransientForSet()) { |
||||
return false; |
||||
} |
||||
|
||||
// 检查@PropIgnore注解
|
||||
return !isIgnoreSet(); |
||||
} |
||||
|
||||
/** |
||||
* 设置Bean的字段值<br> |
||||
* 首先调用字段对应的Setter方法,如果Setter方法不存在,则判断字段如果为public,则直接赋值字段值<br> |
||||
* 此方法不检查任何注解,使用前需调用 {@link #isWritable(boolean)} 检查是否可写 |
||||
* |
||||
* @param bean Bean对象 |
||||
* @param value 值,必须与字段值类型匹配 |
||||
* @return this |
||||
* @since 4.0.5 |
||||
*/ |
||||
public PropDesc setValue(Object bean, Object value) { |
||||
if (null != this.setter) { |
||||
ReflectUtil.invoke(bean, this.setter, value); |
||||
} else if (ModifierUtil.isPublic(this.field)) { |
||||
ReflectUtil.setFieldValue(bean, this.field, value); |
||||
} |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 设置属性值,可以自动转换字段类型为目标类型 |
||||
* |
||||
* @param bean Bean对象 |
||||
* @param value 属性值,可以为任意类型 |
||||
* @param ignoreNull 是否忽略{@code null}值,true表示忽略 |
||||
* @param ignoreError 是否忽略错误,包括转换错误和注入错误 |
||||
* @return this |
||||
* @since 5.4.2 |
||||
*/ |
||||
public PropDesc setValue(Object bean, Object value, boolean ignoreNull, boolean ignoreError) { |
||||
return setValue(bean, value, ignoreNull, ignoreError, true); |
||||
} |
||||
|
||||
/** |
||||
* 设置属性值,可以自动转换字段类型为目标类型 |
||||
* |
||||
* @param bean Bean对象 |
||||
* @param value 属性值,可以为任意类型 |
||||
* @param ignoreNull 是否忽略{@code null}值,true表示忽略 |
||||
* @param ignoreError 是否忽略错误,包括转换错误和注入错误 |
||||
* @param override 是否覆盖目标值,如果不覆盖,会先读取bean的值,{@code null}则写,否则忽略。如果覆盖,则不判断直接写 |
||||
* @return this |
||||
* @since 5.7.17 |
||||
*/ |
||||
public PropDesc setValue(Object bean, Object value, boolean ignoreNull, boolean ignoreError, boolean override) { |
||||
if (null == value && ignoreNull) { |
||||
return this; |
||||
} |
||||
|
||||
// issue#I4JQ1N@Gitee
|
||||
// 非覆盖模式下,如果目标值存在,则跳过
|
||||
if (!override && null != getValue(bean)) { |
||||
return this; |
||||
} |
||||
|
||||
// 当类型不匹配的时候,执行默认转换
|
||||
if (null != value) { |
||||
final Class<?> propClass = getFieldClass(); |
||||
if (!propClass.isInstance(value)) { |
||||
value = Convert.convertWithCheck(propClass, value, null, ignoreError); |
||||
} |
||||
} |
||||
|
||||
// 属性赋值
|
||||
if (null != value || !ignoreNull) { |
||||
try { |
||||
this.setValue(bean, value); |
||||
} catch (Exception e) { |
||||
if (!ignoreError) { |
||||
throw new BeanException(e, "Set value of [{}] error!", getFieldName()); |
||||
} |
||||
// 忽略注入失败
|
||||
} |
||||
} |
||||
|
||||
return this; |
||||
} |
||||
|
||||
//------------------------------------------------------------------------------------ Private method start
|
||||
|
||||
/** |
||||
* 通过Getter和Setter方法中找到属性类型 |
||||
* |
||||
* @param getter Getter方法 |
||||
* @param setter Setter方法 |
||||
* @return {@link Type} |
||||
*/ |
||||
private Type findPropType(Method getter, Method setter) { |
||||
Type type = null; |
||||
if (null != getter) { |
||||
type = TypeUtil.getReturnType(getter); |
||||
} |
||||
if (null == type && null != setter) { |
||||
type = TypeUtil.getParamType(setter, 0); |
||||
} |
||||
return type; |
||||
} |
||||
|
||||
/** |
||||
* 通过Getter和Setter方法中找到属性类型 |
||||
* |
||||
* @param getter Getter方法 |
||||
* @param setter Setter方法 |
||||
* @return {@link Type} |
||||
*/ |
||||
private Class<?> findPropClass(Method getter, Method setter) { |
||||
Class<?> type = null; |
||||
if (null != getter) { |
||||
type = TypeUtil.getReturnClass(getter); |
||||
} |
||||
if (null == type && null != setter) { |
||||
type = TypeUtil.getFirstParamClass(setter); |
||||
} |
||||
return type; |
||||
} |
||||
|
||||
/** |
||||
* 检查字段是否被忽略写,通过{@link PropIgnore} 注解完成,规则为: |
||||
* <pre> |
||||
* 1. 在字段上有{@link PropIgnore} 注解 |
||||
* 2. 在setXXX方法上有{@link PropIgnore} 注解 |
||||
* </pre> |
||||
* |
||||
* @return 是否忽略写 |
||||
* @since 5.4.2 |
||||
*/ |
||||
private boolean isIgnoreSet() { |
||||
return AnnotationUtil.hasAnnotation(this.field, PropIgnore.class) |
||||
|| AnnotationUtil.hasAnnotation(this.setter, PropIgnore.class); |
||||
} |
||||
|
||||
/** |
||||
* 检查字段是否被忽略读,通过{@link PropIgnore} 注解完成,规则为: |
||||
* <pre> |
||||
* 1. 在字段上有{@link PropIgnore} 注解 |
||||
* 2. 在getXXX方法上有{@link PropIgnore} 注解 |
||||
* </pre> |
||||
* |
||||
* @return 是否忽略读 |
||||
* @since 5.4.2 |
||||
*/ |
||||
private boolean isIgnoreGet() { |
||||
return AnnotationUtil.hasAnnotation(this.field, PropIgnore.class) |
||||
|| AnnotationUtil.hasAnnotation(this.getter, PropIgnore.class); |
||||
} |
||||
|
||||
/** |
||||
* 字段和Getter方法是否为Transient关键字修饰的 |
||||
* |
||||
* @return 是否为Transient关键字修饰的 |
||||
* @since 5.3.11 |
||||
*/ |
||||
private boolean isTransientForGet() { |
||||
boolean isTransient = ModifierUtil.hasModifier(this.field, ModifierUtil.ModifierType.TRANSIENT); |
||||
|
||||
// 检查Getter方法
|
||||
if (!isTransient && null != this.getter) { |
||||
isTransient = ModifierUtil.hasModifier(this.getter, ModifierUtil.ModifierType.TRANSIENT); |
||||
|
||||
// 检查注解
|
||||
if (!isTransient) { |
||||
// isTransient = AnnotationUtil.hasAnnotation(this.getter, Transient.class);
|
||||
} |
||||
} |
||||
|
||||
return isTransient; |
||||
} |
||||
|
||||
/** |
||||
* 字段和Getter方法是否为Transient关键字修饰的 |
||||
* |
||||
* @return 是否为Transient关键字修饰的 |
||||
* @since 5.3.11 |
||||
*/ |
||||
private boolean isTransientForSet() { |
||||
boolean isTransient = ModifierUtil.hasModifier(this.field, ModifierUtil.ModifierType.TRANSIENT); |
||||
|
||||
// 检查Getter方法
|
||||
if (!isTransient && null != this.setter) { |
||||
isTransient = ModifierUtil.hasModifier(this.setter, ModifierUtil.ModifierType.TRANSIENT); |
||||
|
||||
// 检查注解
|
||||
if (!isTransient) { |
||||
// isTransient = AnnotationUtil.hasAnnotation(this.setter, Transient.class);
|
||||
} |
||||
} |
||||
|
||||
return isTransient; |
||||
} |
||||
//------------------------------------------------------------------------------------ Private method end
|
||||
} |
@ -0,0 +1,28 @@ |
||||
package cn.hutool.core.bean.copier; |
||||
|
||||
import cn.hutool.core.lang.copier.Copier; |
||||
import cn.hutool.core.util.ObjectUtil; |
||||
|
||||
/** |
||||
* 抽象的对象拷贝封装,提供来源对象、目标对象持有 |
||||
* |
||||
* @param <S> 来源对象类型 |
||||
* @param <T> 目标对象类型 |
||||
* @author looly |
||||
* @since 5.8.0 |
||||
*/ |
||||
public abstract class AbsCopier<S, T> implements Copier<T> { |
||||
|
||||
protected final S source; |
||||
protected final T target; |
||||
/** |
||||
* 拷贝选项 |
||||
*/ |
||||
protected final CopyOptions copyOptions; |
||||
|
||||
public AbsCopier(S source, T target, CopyOptions copyOptions) { |
||||
this.source = source; |
||||
this.target = target; |
||||
this.copyOptions = ObjectUtil.defaultIfNull(copyOptions, CopyOptions::create); |
||||
} |
||||
} |
@ -0,0 +1,94 @@ |
||||
package cn.hutool.core.bean.copier; |
||||
|
||||
import cn.hutool.core.lang.Assert; |
||||
import cn.hutool.core.lang.copier.Copier; |
||||
|
||||
import java.io.Serializable; |
||||
import java.lang.reflect.Type; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* Bean拷贝,提供: |
||||
* |
||||
* <pre> |
||||
* 1. Bean 转 Bean |
||||
* 2. Bean 转 Map |
||||
* 3. Map 转 Bean |
||||
* 4. Map 转 Map |
||||
* </pre> |
||||
* |
||||
* @author looly |
||||
* |
||||
* @param <T> 目标对象类型 |
||||
* @since 3.2.3 |
||||
*/ |
||||
public class BeanCopier<T> implements Copier<T>, Serializable { |
||||
private static final long serialVersionUID = 1L; |
||||
|
||||
private final Copier<T> copier; |
||||
|
||||
/** |
||||
* 创建BeanCopier |
||||
* |
||||
* @param <T> 目标Bean类型 |
||||
* @param source 来源对象,可以是Bean或者Map |
||||
* @param target 目标Bean对象 |
||||
* @param copyOptions 拷贝属性选项 |
||||
* @return BeanCopier |
||||
*/ |
||||
public static <T> BeanCopier<T> create(Object source, T target, CopyOptions copyOptions) { |
||||
return create(source, target, target.getClass(), copyOptions); |
||||
} |
||||
|
||||
/** |
||||
* 创建BeanCopier |
||||
* |
||||
* @param <T> 目标Bean类型 |
||||
* @param source 来源对象,可以是Bean或者Map |
||||
* @param target 目标Bean对象 |
||||
* @param destType 目标的泛型类型,用于标注有泛型参数的Bean对象 |
||||
* @param copyOptions 拷贝属性选项 |
||||
* @return BeanCopier |
||||
*/ |
||||
public static <T> BeanCopier<T> create(Object source, T target, Type destType, CopyOptions copyOptions) { |
||||
return new BeanCopier<>(source, target, destType, copyOptions); |
||||
} |
||||
|
||||
/** |
||||
* 构造 |
||||
* |
||||
* @param source 来源对象,可以是Bean或者Map |
||||
* @param target 目标Bean对象 |
||||
* @param targetType 目标的泛型类型,用于标注有泛型参数的Bean对象 |
||||
* @param copyOptions 拷贝属性选项 |
||||
*/ |
||||
public BeanCopier(Object source, T target, Type targetType, CopyOptions copyOptions) { |
||||
Assert.notNull(source, "Source bean must be not null!"); |
||||
Assert.notNull(target, "Target bean must be not null!"); |
||||
Copier<T> copier; |
||||
if (source instanceof Map) { |
||||
if (target instanceof Map) { |
||||
//noinspection unchecked
|
||||
copier = (Copier<T>) new MapToMapCopier((Map<?, ?>) source, (Map<?, ?>) target, targetType, copyOptions); |
||||
} else { |
||||
copier = new MapToBeanCopier<>((Map<?, ?>) source, target, targetType, copyOptions); |
||||
} |
||||
}else if(source instanceof ValueProvider){ |
||||
//noinspection unchecked
|
||||
copier = new ValueProviderToBeanCopier<>((ValueProvider<String>) source, target, targetType, copyOptions); |
||||
} else { |
||||
if (target instanceof Map) { |
||||
//noinspection unchecked
|
||||
copier = (Copier<T>) new BeanToMapCopier(source, (Map<?, ?>) target, targetType, copyOptions); |
||||
} else { |
||||
copier = new BeanToBeanCopier<>(source, target, targetType, copyOptions); |
||||
} |
||||
} |
||||
this.copier = copier; |
||||
} |
||||
|
||||
@Override |
||||
public T copy() { |
||||
return copier.copy(); |
||||
} |
||||
} |
@ -0,0 +1,91 @@ |
||||
package cn.hutool.core.bean.copier; |
||||
|
||||
import cn.hutool.core.bean.BeanUtil; |
||||
import cn.hutool.core.bean.PropDesc; |
||||
import cn.hutool.core.lang.Assert; |
||||
import cn.hutool.core.util.TypeUtil; |
||||
|
||||
import java.lang.reflect.Type; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* Bean属性拷贝到Bean中的拷贝器 |
||||
* |
||||
* @param <S> 源Bean类型 |
||||
* @param <T> 目标Bean类型 |
||||
* @since 5.8.0 |
||||
*/ |
||||
public class BeanToBeanCopier<S, T> extends AbsCopier<S, T> { |
||||
|
||||
/** |
||||
* 目标的类型(用于泛型类注入) |
||||
*/ |
||||
private final Type targetType; |
||||
|
||||
/** |
||||
* 构造 |
||||
* |
||||
* @param source 来源Map |
||||
* @param target 目标Bean对象 |
||||
* @param targetType 目标泛型类型 |
||||
* @param copyOptions 拷贝选项 |
||||
*/ |
||||
public BeanToBeanCopier(S source, T target, Type targetType, CopyOptions copyOptions) { |
||||
super(source, target, copyOptions); |
||||
this.targetType = targetType; |
||||
} |
||||
|
||||
@Override |
||||
public T copy() { |
||||
Class<?> actualEditable = target.getClass(); |
||||
if (null != copyOptions.editable) { |
||||
// 检查限制类是否为target的父类或接口
|
||||
Assert.isTrue(copyOptions.editable.isInstance(target), |
||||
"Target class [{}] not assignable to Editable class [{}]", actualEditable.getName(), copyOptions.editable.getName()); |
||||
actualEditable = copyOptions.editable; |
||||
} |
||||
final Map<String, PropDesc> targetPropDescMap = BeanUtil.getBeanDesc(actualEditable).getPropMap(copyOptions.ignoreCase); |
||||
|
||||
final Map<String, PropDesc> sourcePropDescMap = BeanUtil.getBeanDesc(source.getClass()).getPropMap(copyOptions.ignoreCase); |
||||
sourcePropDescMap.forEach((sFieldName, sDesc) -> { |
||||
if (null == sFieldName || !sDesc.isReadable(copyOptions.transientSupport)) { |
||||
// 字段空或不可读,跳过
|
||||
return; |
||||
} |
||||
|
||||
sFieldName = copyOptions.editFieldName(sFieldName); |
||||
// 对key做转换,转换后为null的跳过
|
||||
if (null == sFieldName) { |
||||
return; |
||||
} |
||||
|
||||
// 忽略不需要拷贝的 key,
|
||||
if (!copyOptions.testKeyFilter(sFieldName)) { |
||||
return; |
||||
} |
||||
|
||||
// 检查目标字段可写性
|
||||
final PropDesc tDesc = targetPropDescMap.get(sFieldName); |
||||
if (null == tDesc || !tDesc.isWritable(this.copyOptions.transientSupport)) { |
||||
// 字段不可写,跳过之
|
||||
return; |
||||
} |
||||
|
||||
// 检查源对象属性是否过滤属性
|
||||
Object sValue = sDesc.getValue(this.source); |
||||
if (!copyOptions.testPropertyFilter(sDesc.getField(), sValue)) { |
||||
return; |
||||
} |
||||
|
||||
// 获取目标字段真实类型并转换源值
|
||||
final Type fieldType = TypeUtil.getActualType(this.targetType, tDesc.getFieldType()); |
||||
//sValue = Convert.convertWithCheck(fieldType, sValue, null, this.copyOptions.ignoreError);
|
||||
sValue = this.copyOptions.convertField(fieldType, sValue); |
||||
sValue = copyOptions.editFieldValue(sFieldName, sValue); |
||||
|
||||
// 目标赋值
|
||||
tDesc.setValue(this.target, sValue, copyOptions.ignoreNullValue, copyOptions.ignoreError, copyOptions.override); |
||||
}); |
||||
return this.target; |
||||
} |
||||
} |
@ -0,0 +1,87 @@ |
||||
package cn.hutool.core.bean.copier; |
||||
|
||||
import cn.hutool.core.bean.BeanUtil; |
||||
import cn.hutool.core.bean.PropDesc; |
||||
import cn.hutool.core.lang.Assert; |
||||
import cn.hutool.core.util.TypeUtil; |
||||
|
||||
import java.lang.reflect.Type; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* Bean属性拷贝到Map中的拷贝器 |
||||
* |
||||
* @since 5.8.0 |
||||
*/ |
||||
@SuppressWarnings("rawtypes") |
||||
public class BeanToMapCopier extends AbsCopier<Object, Map> { |
||||
|
||||
/** |
||||
* 目标的Map类型(用于泛型类注入) |
||||
*/ |
||||
private final Type targetType; |
||||
|
||||
/** |
||||
* 构造 |
||||
* |
||||
* @param source 来源Map |
||||
* @param target 目标Map对象 |
||||
* @param targetType 目标泛型类型 |
||||
* @param copyOptions 拷贝选项 |
||||
*/ |
||||
public BeanToMapCopier(Object source, Map target, Type targetType, CopyOptions copyOptions) { |
||||
super(source, target, copyOptions); |
||||
this.targetType = targetType; |
||||
} |
||||
|
||||
@Override |
||||
public Map copy() { |
||||
Class<?> actualEditable = source.getClass(); |
||||
if (null != copyOptions.editable) { |
||||
// 检查限制类是否为target的父类或接口
|
||||
Assert.isTrue(copyOptions.editable.isInstance(source), |
||||
"Source class [{}] not assignable to Editable class [{}]", actualEditable.getName(), copyOptions.editable.getName()); |
||||
actualEditable = copyOptions.editable; |
||||
} |
||||
|
||||
final Map<String, PropDesc> sourcePropDescMap = BeanUtil.getBeanDesc(actualEditable).getPropMap(copyOptions.ignoreCase); |
||||
sourcePropDescMap.forEach((sFieldName, sDesc) -> { |
||||
if (null == sFieldName || !sDesc.isReadable(copyOptions.transientSupport)) { |
||||
// 字段空或不可读,跳过
|
||||
return; |
||||
} |
||||
|
||||
sFieldName = copyOptions.editFieldName(sFieldName); |
||||
// 对key做转换,转换后为null的跳过
|
||||
if (null == sFieldName) { |
||||
return; |
||||
} |
||||
|
||||
// 忽略不需要拷贝的 key,
|
||||
if (!copyOptions.testKeyFilter(sFieldName)) { |
||||
return; |
||||
} |
||||
|
||||
// 检查源对象属性是否过滤属性
|
||||
Object sValue = sDesc.getValue(this.source); |
||||
if (!copyOptions.testPropertyFilter(sDesc.getField(), sValue)) { |
||||
return; |
||||
} |
||||
|
||||
// 获取目标值真实类型并转换源值
|
||||
final Type[] typeArguments = TypeUtil.getTypeArguments(this.targetType); |
||||
if(null != typeArguments){ |
||||
//sValue = Convert.convertWithCheck(typeArguments[1], sValue, null, this.copyOptions.ignoreError);
|
||||
sValue = this.copyOptions.convertField(typeArguments[1], sValue); |
||||
sValue = copyOptions.editFieldValue(sFieldName, sValue); |
||||
} |
||||
|
||||
// 目标赋值
|
||||
if(null != sValue || !copyOptions.ignoreNullValue){ |
||||
//noinspection unchecked
|
||||
target.put(sFieldName, sValue); |
||||
} |
||||
}); |
||||
return this.target; |
||||
} |
||||
} |
@ -0,0 +1,383 @@ |
||||
package cn.hutool.core.bean.copier; |
||||
|
||||
import cn.hutool.core.collection.CollUtil; |
||||
import cn.hutool.core.convert.Convert; |
||||
import cn.hutool.core.convert.TypeConverter; |
||||
import cn.hutool.core.lang.Editor; |
||||
import cn.hutool.core.lang.func.Func1; |
||||
import cn.hutool.core.lang.func.LambdaUtil; |
||||
import cn.hutool.core.util.ArrayUtil; |
||||
import cn.hutool.core.util.ObjectUtil; |
||||
import cn.hutool.core.util.ReflectUtil; |
||||
|
||||
import java.io.Serializable; |
||||
import java.lang.reflect.Field; |
||||
import java.lang.reflect.Type; |
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
import java.util.function.BiFunction; |
||||
import java.util.function.BiPredicate; |
||||
|
||||
/** |
||||
* 属性拷贝选项<br> |
||||
* 包括:<br> |
||||
* 1、限制的类或接口,必须为目标对象的实现接口或父类,用于限制拷贝的属性,例如一个类我只想复制其父类的一些属性,就可以将editable设置为父类<br> |
||||
* 2、是否忽略空值,当源对象的值为null时,true: 忽略而不注入此值,false: 注入null<br> |
||||
* 3、忽略的属性列表,设置一个属性列表,不拷贝这些属性值<br> |
||||
* |
||||
* @author Looly |
||||
*/ |
||||
public class CopyOptions implements Serializable { |
||||
private static final long serialVersionUID = 1L; |
||||
|
||||
/** |
||||
* 限制的类或接口,必须为目标对象的实现接口或父类,用于限制拷贝的属性,例如一个类我只想复制其父类的一些属性,就可以将editable设置为父类<br> |
||||
* 如果目标对象是Map,源对象是Bean,则作用于源对象上 |
||||
*/ |
||||
protected Class<?> editable; |
||||
/** |
||||
* 是否忽略空值,当源对象的值为null时,true: 忽略而不注入此值,false: 注入null |
||||
*/ |
||||
protected boolean ignoreNullValue; |
||||
/** |
||||
* 属性过滤器,断言通过的属性才会被复制<br> |
||||
* 断言参数中Field为源对象的字段对象,如果源对象为Map,使用目标对象,Object为源对象的对应值 |
||||
*/ |
||||
private BiPredicate<Field, Object> propertiesFilter; |
||||
/** |
||||
* 是否忽略字段注入错误 |
||||
*/ |
||||
protected boolean ignoreError; |
||||
/** |
||||
* 是否忽略字段大小写 |
||||
*/ |
||||
protected boolean ignoreCase; |
||||
/** |
||||
* 字段属性编辑器,用于自定义属性转换规则,例如驼峰转下划线等<br> |
||||
* 规则为,{@link Editor#edit(Object)}属性为源对象的字段名称或key,返回值为目标对象的字段名称或key |
||||
*/ |
||||
private Editor<String> fieldNameEditor; |
||||
/** |
||||
* 字段属性值编辑器,用于自定义属性值转换规则,例如null转""等 |
||||
*/ |
||||
protected BiFunction<String, Object, Object> fieldValueEditor; |
||||
/** |
||||
* 是否支持transient关键字修饰和@Transient注解,如果支持,被修饰的字段或方法对应的字段将被忽略。 |
||||
*/ |
||||
protected boolean transientSupport = true; |
||||
/** |
||||
* 是否覆盖目标值,如果不覆盖,会先读取目标对象的值,非{@code null}则写,否则忽略。如果覆盖,则不判断直接写 |
||||
*/ |
||||
protected boolean override = true; |
||||
|
||||
/** |
||||
* 源对象和目标对象都是 {@code Map} 时, 需要忽略的源对象 {@code Map} key |
||||
*/ |
||||
private Set<String> ignoreKeySet; |
||||
|
||||
/** |
||||
* 自定义类型转换器,默认使用全局万能转换器转换 |
||||
*/ |
||||
protected TypeConverter converter = (type, value) -> { |
||||
if(null == value){ |
||||
return null; |
||||
} |
||||
|
||||
final String name = value.getClass().getName(); |
||||
if(ArrayUtil.contains(new String[]{"cn.hutool.json.JSONObject", "cn.hutool.json.JSONArray"}, name)){ |
||||
// 由于设计缺陷导致JSON转Bean时无法使用自定义的反序列化器,此处采用反射方式修复bug,此类问题会在6.x解决
|
||||
return ReflectUtil.invoke(value, "toBean", ObjectUtil.defaultIfNull(type, Object.class)); |
||||
} |
||||
|
||||
return Convert.convertWithCheck(type, value, null, ignoreError); |
||||
}; |
||||
|
||||
//region create
|
||||
|
||||
/** |
||||
* 创建拷贝选项 |
||||
* |
||||
* @return 拷贝选项 |
||||
*/ |
||||
public static CopyOptions create() { |
||||
return new CopyOptions(); |
||||
} |
||||
|
||||
/** |
||||
* 创建拷贝选项 |
||||
* |
||||
* @param editable 限制的类或接口,必须为目标对象的实现接口或父类,用于限制拷贝的属性 |
||||
* @param ignoreNullValue 是否忽略空值,当源对象的值为null时,true: 忽略而不注入此值,false: 注入null |
||||
* @param ignoreProperties 忽略的属性列表,设置一个属性列表,不拷贝这些属性值 |
||||
* @return 拷贝选项 |
||||
*/ |
||||
public static CopyOptions create(Class<?> editable, boolean ignoreNullValue, String... ignoreProperties) { |
||||
return new CopyOptions(editable, ignoreNullValue, ignoreProperties); |
||||
} |
||||
//endregion
|
||||
|
||||
/** |
||||
* 构造拷贝选项 |
||||
*/ |
||||
public CopyOptions() { |
||||
} |
||||
|
||||
/** |
||||
* 构造拷贝选项 |
||||
* |
||||
* @param editable 限制的类或接口,必须为目标对象的实现接口或父类,用于限制拷贝的属性 |
||||
* @param ignoreNullValue 是否忽略空值,当源对象的值为null时,true: 忽略而不注入此值,false: 注入null |
||||
* @param ignoreProperties 忽略的目标对象中属性列表,设置一个属性列表,不拷贝这些属性值 |
||||
*/ |
||||
public CopyOptions(Class<?> editable, boolean ignoreNullValue, String... ignoreProperties) { |
||||
this.propertiesFilter = (f, v) -> true; |
||||
this.editable = editable; |
||||
this.ignoreNullValue = ignoreNullValue; |
||||
this.setIgnoreProperties(ignoreProperties); |
||||
} |
||||
|
||||
/** |
||||
* 设置限制的类或接口,必须为目标对象的实现接口或父类,用于限制拷贝的属性 |
||||
* |
||||
* @param editable 限制的类或接口 |
||||
* @return CopyOptions |
||||
*/ |
||||
public CopyOptions setEditable(Class<?> editable) { |
||||
this.editable = editable; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 设置是否忽略空值,当源对象的值为null时,true: 忽略而不注入此值,false: 注入null |
||||
* |
||||
* @param ignoreNullVall 是否忽略空值,当源对象的值为null时,true: 忽略而不注入此值,false: 注入null |
||||
* @return CopyOptions |
||||
*/ |
||||
public CopyOptions setIgnoreNullValue(boolean ignoreNullVall) { |
||||
this.ignoreNullValue = ignoreNullVall; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 设置忽略空值,当源对象的值为null时,忽略而不注入此值 |
||||
* |
||||
* @return CopyOptions |
||||
* @since 4.5.7 |
||||
*/ |
||||
public CopyOptions ignoreNullValue() { |
||||
return setIgnoreNullValue(true); |
||||
} |
||||
|
||||
/** |
||||
* 属性过滤器,断言通过的属性才会被复制<br> |
||||
* {@link BiPredicate#test(Object, Object)}返回{@code true}则属性通过,{@code false}不通过,抛弃之 |
||||
* |
||||
* @param propertiesFilter 属性过滤器 |
||||
* @return CopyOptions |
||||
*/ |
||||
public CopyOptions setPropertiesFilter(BiPredicate<Field, Object> propertiesFilter) { |
||||
this.propertiesFilter = propertiesFilter; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 设置忽略的目标对象中属性列表,设置一个属性列表,不拷贝这些属性值 |
||||
* |
||||
* @param ignoreProperties 忽略的目标对象中属性列表,设置一个属性列表,不拷贝这些属性值 |
||||
* @return CopyOptions |
||||
*/ |
||||
public CopyOptions setIgnoreProperties(String... ignoreProperties) { |
||||
this.ignoreKeySet = CollUtil.newHashSet(ignoreProperties); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 设置忽略的目标对象中属性列表,设置一个属性列表,不拷贝这些属性值,Lambda方式 |
||||
* |
||||
* @param <P> 参数类型 |
||||
* @param <R> 返回值类型 |
||||
* @param funcs 忽略的目标对象中属性列表,设置一个属性列表,不拷贝这些属性值 |
||||
* @return CopyOptions |
||||
* @since 5.8.0 |
||||
*/ |
||||
@SuppressWarnings("unchecked") |
||||
public <P, R> CopyOptions setIgnoreProperties(Func1<P, R>... funcs) { |
||||
this.ignoreKeySet = ArrayUtil.mapToSet(funcs, LambdaUtil::getFieldName); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 设置是否忽略字段的注入错误 |
||||
* |
||||
* @param ignoreError 是否忽略注入错误 |
||||
* @return CopyOptions |
||||
*/ |
||||
public CopyOptions setIgnoreError(boolean ignoreError) { |
||||
this.ignoreError = ignoreError; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 设置忽略字段的注入错误 |
||||
* |
||||
* @return CopyOptions |
||||
* @since 4.5.7 |
||||
*/ |
||||
public CopyOptions ignoreError() { |
||||
return setIgnoreError(true); |
||||
} |
||||
|
||||
/** |
||||
* 设置是否忽略字段的大小写 |
||||
* |
||||
* @param ignoreCase 是否忽略大小写 |
||||
* @return CopyOptions |
||||
*/ |
||||
public CopyOptions setIgnoreCase(boolean ignoreCase) { |
||||
this.ignoreCase = ignoreCase; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 设置忽略字段的大小写 |
||||
* |
||||
* @return CopyOptions |
||||
* @since 4.5.7 |
||||
*/ |
||||
public CopyOptions ignoreCase() { |
||||
return setIgnoreCase(true); |
||||
} |
||||
|
||||
/** |
||||
* 设置拷贝属性的字段映射,用于不同的属性之前拷贝做对应表用<br> |
||||
* 需要注意的是,当使用ValueProvider作为数据提供者时,这个映射是相反的,即fieldMapping中key为目标Bean的名称,而value是提供者中的key |
||||
* |
||||
* @param fieldMapping 拷贝属性的字段映射,用于不同的属性之前拷贝做对应表用 |
||||
* @return CopyOptions |
||||
*/ |
||||
public CopyOptions setFieldMapping(Map<String, String> fieldMapping) { |
||||
return setFieldNameEditor((key -> fieldMapping.getOrDefault(key, key))); |
||||
} |
||||
|
||||
/** |
||||
* 设置字段属性编辑器,用于自定义属性转换规则,例如驼峰转下划线等<br> |
||||
* 此转换器只针对源端的字段做转换,请确认转换后与目标端字段一致<br> |
||||
* 当转换后的字段名为null时忽略这个字段<br> |
||||
* 需要注意的是,当使用ValueProvider作为数据提供者时,这个映射是相反的,即fieldMapping中key为目标Bean的名称,而value是提供者中的key |
||||
* |
||||
* @param fieldNameEditor 字段属性编辑器,用于自定义属性转换规则,例如驼峰转下划线等 |
||||
* @return CopyOptions |
||||
* @since 5.4.2 |
||||
*/ |
||||
public CopyOptions setFieldNameEditor(Editor<String> fieldNameEditor) { |
||||
this.fieldNameEditor = fieldNameEditor; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 设置字段属性值编辑器,用于自定义属性值转换规则,例如null转""等<br> |
||||
* |
||||
* @param fieldValueEditor 字段属性值编辑器,用于自定义属性值转换规则,例如null转""等 |
||||
* @return CopyOptions |
||||
* @since 5.7.15 |
||||
*/ |
||||
public CopyOptions setFieldValueEditor(BiFunction<String, Object, Object> fieldValueEditor) { |
||||
this.fieldValueEditor = fieldValueEditor; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 编辑字段值 |
||||
* |
||||
* @param fieldName 字段名 |
||||
* @param fieldValue 字段值 |
||||
* @return 编辑后的字段值 |
||||
* @since 5.7.15 |
||||
*/ |
||||
protected Object editFieldValue(String fieldName, Object fieldValue) { |
||||
return (null != this.fieldValueEditor) ? |
||||
this.fieldValueEditor.apply(fieldName, fieldValue) : fieldValue; |
||||
} |
||||
|
||||
/** |
||||
* 设置是否支持transient关键字修饰和@Transient注解,如果支持,被修饰的字段或方法对应的字段将被忽略。 |
||||
* |
||||
* @param transientSupport 是否支持 |
||||
* @return this |
||||
* @since 5.4.2 |
||||
*/ |
||||
public CopyOptions setTransientSupport(boolean transientSupport) { |
||||
this.transientSupport = transientSupport; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 设置是否覆盖目标值,如果不覆盖,会先读取目标对象的值,为{@code null}则写,否则忽略。如果覆盖,则不判断直接写 |
||||
* |
||||
* @param override 是否覆盖目标值 |
||||
* @return this |
||||
* @since 5.7.17 |
||||
*/ |
||||
public CopyOptions setOverride(boolean override) { |
||||
this.override = override; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 设置自定义类型转换器,默认使用全局万能转换器转换。 |
||||
* |
||||
* @param converter 转换器 |
||||
* @return this |
||||
* @since 5.8.0 |
||||
*/ |
||||
public CopyOptions setConverter(TypeConverter converter) { |
||||
this.converter = converter; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 使用自定义转换器转换字段值<br> |
||||
* 如果自定义转换器为{@code null},则返回原值。 |
||||
* |
||||
* @param targetType 目标类型 |
||||
* @param fieldValue 字段值 |
||||
* @return 编辑后的字段值 |
||||
* @since 5.8.0 |
||||
*/ |
||||
protected Object convertField(Type targetType, Object fieldValue) { |
||||
return (null != this.converter) ? |
||||
this.converter.convert(targetType, fieldValue) : fieldValue; |
||||
} |
||||
|
||||
/** |
||||
* 转换字段名为编辑后的字段名 |
||||
* |
||||
* @param fieldName 字段名 |
||||
* @return 编辑后的字段名 |
||||
* @since 5.4.2 |
||||
*/ |
||||
protected String editFieldName(String fieldName) { |
||||
return (null != this.fieldNameEditor) ? this.fieldNameEditor.edit(fieldName) : fieldName; |
||||
} |
||||
|
||||
/** |
||||
* 测试是否保留字段,{@code true}保留,{@code false}不保留 |
||||
* |
||||
* @param field 字段 |
||||
* @param value 值 |
||||
* @return 是否保留 |
||||
*/ |
||||
protected boolean testPropertyFilter(Field field, Object value) { |
||||
return null == this.propertiesFilter || this.propertiesFilter.test(field, value); |
||||
} |
||||
|
||||
/** |
||||
* 测试是否保留key, {@code true} 不保留, {@code false} 保留 |
||||
* |
||||
* @param key {@link Map} key |
||||
* @return 是否保留 |
||||
*/ |
||||
protected boolean testKeyFilter(Object key) { |
||||
return CollUtil.isEmpty(this.ignoreKeySet) || !this.ignoreKeySet.contains(key); |
||||
} |
||||
} |
@ -0,0 +1,119 @@ |
||||
package cn.hutool.core.bean.copier; |
||||
|
||||
import cn.hutool.core.bean.BeanUtil; |
||||
import cn.hutool.core.bean.PropDesc; |
||||
import cn.hutool.core.lang.Assert; |
||||
import cn.hutool.core.map.CaseInsensitiveMap; |
||||
import cn.hutool.core.map.MapWrapper; |
||||
import cn.hutool.core.util.StrUtil; |
||||
import cn.hutool.core.util.TypeUtil; |
||||
|
||||
import java.lang.reflect.Type; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* Map属性拷贝到Bean中的拷贝器 |
||||
* |
||||
* @param <T> 目标Bean类型 |
||||
* @since 5.8.0 |
||||
*/ |
||||
public class MapToBeanCopier<T> extends AbsCopier<Map<?, ?>, T> { |
||||
|
||||
/** |
||||
* 目标的类型(用于泛型类注入) |
||||
*/ |
||||
private final Type targetType; |
||||
|
||||
/** |
||||
* 构造 |
||||
* |
||||
* @param source 来源Map |
||||
* @param target 目标Bean对象 |
||||
* @param targetType 目标泛型类型 |
||||
* @param copyOptions 拷贝选项 |
||||
*/ |
||||
public MapToBeanCopier(Map<?, ?> source, T target, Type targetType, CopyOptions copyOptions) { |
||||
super(source, target, copyOptions); |
||||
|
||||
// 针对MapWrapper特殊处理,提供的Map包装了忽略大小写的Map,则默认转Bean的时候也忽略大小写,如JSONObject
|
||||
if(source instanceof MapWrapper){ |
||||
final Map<?, ?> raw = ((MapWrapper<?, ?>) source).getRaw(); |
||||
if(raw instanceof CaseInsensitiveMap){ |
||||
copyOptions.setIgnoreCase(true); |
||||
} |
||||
} |
||||
|
||||
this.targetType = targetType; |
||||
} |
||||
|
||||
@Override |
||||
public T copy() { |
||||
Class<?> actualEditable = target.getClass(); |
||||
if (null != copyOptions.editable) { |
||||
// 检查限制类是否为target的父类或接口
|
||||
Assert.isTrue(copyOptions.editable.isInstance(target), |
||||
"Target class [{}] not assignable to Editable class [{}]", actualEditable.getName(), copyOptions.editable.getName()); |
||||
actualEditable = copyOptions.editable; |
||||
} |
||||
final Map<String, PropDesc> targetPropDescMap = BeanUtil.getBeanDesc(actualEditable).getPropMap(copyOptions.ignoreCase); |
||||
|
||||
this.source.forEach((sKey, sValue) -> { |
||||
if (null == sKey) { |
||||
return; |
||||
} |
||||
String sKeyStr = copyOptions.editFieldName(sKey.toString()); |
||||
// 对key做转换,转换后为null的跳过
|
||||
if (null == sKeyStr) { |
||||
return; |
||||
} |
||||
|
||||
// 忽略不需要拷贝的 key,
|
||||
if (!copyOptions.testKeyFilter(sKeyStr)) { |
||||
return; |
||||
} |
||||
|
||||
// 检查目标字段可写性
|
||||
final PropDesc tDesc = findPropDesc(targetPropDescMap, sKeyStr); |
||||
if (null == tDesc || !tDesc.isWritable(this.copyOptions.transientSupport)) { |
||||
// 字段不可写,跳过之
|
||||
return; |
||||
} |
||||
sKeyStr = tDesc.getFieldName(); |
||||
|
||||
// 检查目标是否过滤属性
|
||||
if (!copyOptions.testPropertyFilter(tDesc.getField(), sValue)) { |
||||
return; |
||||
} |
||||
|
||||
// 获取目标字段真实类型并转换源值
|
||||
final Type fieldType = TypeUtil.getActualType(this.targetType, tDesc.getFieldType()); |
||||
//Object newValue = Convert.convertWithCheck(fieldType, sValue, null, this.copyOptions.ignoreError);
|
||||
Object newValue = this.copyOptions.convertField(fieldType, sValue); |
||||
newValue = copyOptions.editFieldValue(sKeyStr, newValue); |
||||
|
||||
// 目标赋值
|
||||
tDesc.setValue(this.target, newValue, copyOptions.ignoreNullValue, copyOptions.ignoreError, copyOptions.override); |
||||
}); |
||||
return this.target; |
||||
} |
||||
|
||||
/** |
||||
* 查找Map对应Bean的名称<br> |
||||
* 尝试原名称、转驼峰名称、isXxx去掉is的名称 |
||||
* |
||||
* @param targetPropDescMap 目标bean的属性描述Map |
||||
* @param sKeyStr 键或字段名 |
||||
* @return {@link PropDesc} |
||||
*/ |
||||
private PropDesc findPropDesc(Map<String, PropDesc> targetPropDescMap, String sKeyStr){ |
||||
PropDesc propDesc = targetPropDescMap.get(sKeyStr); |
||||
if(null != propDesc){ |
||||
return propDesc; |
||||
} |
||||
|
||||
// 转驼峰尝试查找
|
||||
sKeyStr = StrUtil.toCamelCase(sKeyStr); |
||||
propDesc = targetPropDescMap.get(sKeyStr); |
||||
return propDesc; |
||||
} |
||||
} |
@ -0,0 +1,75 @@ |
||||
package cn.hutool.core.bean.copier; |
||||
|
||||
import cn.hutool.core.util.TypeUtil; |
||||
|
||||
import java.lang.reflect.Type; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* Map属性拷贝到Map中的拷贝器 |
||||
* |
||||
* @since 5.8.0 |
||||
*/ |
||||
@SuppressWarnings({"rawtypes", "unchecked"}) |
||||
public class MapToMapCopier extends AbsCopier<Map, Map> { |
||||
|
||||
/** |
||||
* 目标的类型(用于泛型类注入) |
||||
*/ |
||||
private final Type targetType; |
||||
|
||||
/** |
||||
* 构造 |
||||
* |
||||
* @param source 来源Map |
||||
* @param target 目标Bean对象 |
||||
* @param targetType 目标泛型类型 |
||||
* @param copyOptions 拷贝选项 |
||||
*/ |
||||
public MapToMapCopier(Map source, Map target, Type targetType, CopyOptions copyOptions) { |
||||
super(source, target, copyOptions); |
||||
this.targetType = targetType; |
||||
} |
||||
|
||||
@Override |
||||
public Map copy() { |
||||
this.source.forEach((sKey, sValue) -> { |
||||
if (null == sKey) { |
||||
return; |
||||
} |
||||
// 忽略空值
|
||||
if (copyOptions.ignoreNullValue && sValue == null) { |
||||
return; |
||||
} |
||||
|
||||
final String sKeyStr = copyOptions.editFieldName(sKey.toString()); |
||||
// 对key做转换,转换后为null的跳过
|
||||
if (null == sKeyStr) { |
||||
return; |
||||
} |
||||
|
||||
// 忽略不需要拷贝的 key,
|
||||
if (!copyOptions.testKeyFilter(sKeyStr)) { |
||||
return; |
||||
} |
||||
|
||||
final Object targetValue = target.get(sKeyStr); |
||||
// 非覆盖模式下,如果目标值存在,则跳过
|
||||
if (!copyOptions.override && null != targetValue) { |
||||
return; |
||||
} |
||||
|
||||
// 获取目标值真实类型并转换源值
|
||||
final Type[] typeArguments = TypeUtil.getTypeArguments(this.targetType); |
||||
if (null != typeArguments) { |
||||
//sValue = Convert.convertWithCheck(typeArguments[1], sValue, null, this.copyOptions.ignoreError);
|
||||
sValue = this.copyOptions.convertField(typeArguments[1], sValue); |
||||
sValue = copyOptions.editFieldValue(sKeyStr, sValue); |
||||
} |
||||
|
||||
// 目标赋值
|
||||
target.put(sKeyStr, sValue); |
||||
}); |
||||
return this.target; |
||||
} |
||||
} |
@ -0,0 +1,34 @@ |
||||
package cn.hutool.core.bean.copier; |
||||
|
||||
import java.lang.reflect.Type; |
||||
|
||||
/** |
||||
* 值提供者,用于提供Bean注入时参数对应值得抽象接口<br> |
||||
* 继承或匿名实例化此接口<br> |
||||
* 在Bean注入过程中,Bean获得字段名,通过外部方式根据这个字段名查找相应的字段值,然后注入Bean<br> |
||||
* |
||||
* @author Looly |
||||
* @param <T> KEY类型,一般情况下为 {@link String} |
||||
* |
||||
*/ |
||||
public interface ValueProvider<T>{ |
||||
|
||||
/** |
||||
* 获取值<br> |
||||
* 返回值一般需要匹配被注入类型,如果不匹配会调用默认转换 Convert#convert(Type, Object)实现转换 |
||||
* |
||||
* @param key Bean对象中参数名 |
||||
* @param valueType 被注入的值的类型 |
||||
* @return 对应参数名的值 |
||||
*/ |
||||
Object value(T key, Type valueType); |
||||
|
||||
/** |
||||
* 是否包含指定KEY,如果不包含则忽略注入<br> |
||||
* 此接口方法单独需要实现的意义在于:有些值提供者(比如Map)key是存在的,但是value为null,此时如果需要注入这个null,需要根据此方法判断 |
||||
* |
||||
* @param key Bean对象中参数名 |
||||
* @return 是否包含指定KEY |
||||
*/ |
||||
boolean containsKey(T key); |
||||
} |
@ -0,0 +1,89 @@ |
||||
package cn.hutool.core.bean.copier; |
||||
|
||||
import cn.hutool.core.bean.BeanUtil; |
||||
import cn.hutool.core.bean.PropDesc; |
||||
import cn.hutool.core.lang.Assert; |
||||
import cn.hutool.core.util.TypeUtil; |
||||
|
||||
import java.lang.reflect.Type; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* {@link ValueProvider}属性拷贝到Bean中的拷贝器 |
||||
* |
||||
* @param <T> 目标Bean类型 |
||||
* @since 5.8.0 |
||||
*/ |
||||
public class ValueProviderToBeanCopier<T> extends AbsCopier<ValueProvider<String>, T> { |
||||
|
||||
/** |
||||
* 目标的类型(用于泛型类注入) |
||||
*/ |
||||
private final Type targetType; |
||||
|
||||
/** |
||||
* 构造 |
||||
* |
||||
* @param source 来源Map |
||||
* @param target 目标Bean对象 |
||||
* @param targetType 目标泛型类型 |
||||
* @param copyOptions 拷贝选项 |
||||
*/ |
||||
public ValueProviderToBeanCopier(ValueProvider<String> source, T target, Type targetType, CopyOptions copyOptions) { |
||||
super(source, target, copyOptions); |
||||
this.targetType = targetType; |
||||
} |
||||
|
||||
@Override |
||||
public T copy() { |
||||
Class<?> actualEditable = target.getClass(); |
||||
if (null != copyOptions.editable) { |
||||
// 检查限制类是否为target的父类或接口
|
||||
Assert.isTrue(copyOptions.editable.isInstance(target), |
||||
"Target class [{}] not assignable to Editable class [{}]", actualEditable.getName(), copyOptions.editable.getName()); |
||||
actualEditable = copyOptions.editable; |
||||
} |
||||
final Map<String, PropDesc> targetPropDescMap = BeanUtil.getBeanDesc(actualEditable).getPropMap(copyOptions.ignoreCase); |
||||
|
||||
targetPropDescMap.forEach((tFieldName, tDesc) -> { |
||||
if (null == tFieldName) { |
||||
return; |
||||
} |
||||
tFieldName = copyOptions.editFieldName(tFieldName); |
||||
// 对key做转换,转换后为null的跳过
|
||||
if (null == tFieldName) { |
||||
return; |
||||
} |
||||
|
||||
// 无字段内容跳过
|
||||
if(!source.containsKey(tFieldName)){ |
||||
return; |
||||
} |
||||
|
||||
// 忽略不需要拷贝的 key,
|
||||
if (!copyOptions.testKeyFilter(tFieldName)) { |
||||
return; |
||||
} |
||||
|
||||
// 检查目标字段可写性
|
||||
if (null == tDesc || !tDesc.isWritable(this.copyOptions.transientSupport)) { |
||||
// 字段不可写,跳过之
|
||||
return; |
||||
} |
||||
|
||||
// 获取目标字段真实类型
|
||||
final Type fieldType = TypeUtil.getActualType(this.targetType ,tDesc.getFieldType()); |
||||
|
||||
// 检查目标对象属性是否过滤属性
|
||||
Object sValue = source.value(tFieldName, fieldType); |
||||
if (!copyOptions.testPropertyFilter(tDesc.getField(), sValue)) { |
||||
return; |
||||
} |
||||
sValue = copyOptions.editFieldValue(tFieldName, sValue); |
||||
|
||||
// 目标赋值
|
||||
tDesc.setValue(this.target, sValue, copyOptions.ignoreNullValue, copyOptions.ignoreError, copyOptions.override); |
||||
}); |
||||
return this.target; |
||||
} |
||||
} |
@ -0,0 +1,7 @@ |
||||
/** |
||||
* Bean拷贝实现,包括拷贝选项等 |
||||
* |
||||
* @author looly |
||||
* |
||||
*/ |
||||
package cn.hutool.core.bean.copier; |
@ -0,0 +1,100 @@ |
||||
package cn.hutool.core.bean.copier.provider; |
||||
|
||||
import cn.hutool.core.bean.BeanUtil; |
||||
import cn.hutool.core.bean.PropDesc; |
||||
import cn.hutool.core.bean.copier.ValueProvider; |
||||
import cn.hutool.core.lang.Editor; |
||||
import cn.hutool.core.map.FuncKeyMap; |
||||
import cn.hutool.core.util.StrUtil; |
||||
|
||||
import java.io.Serializable; |
||||
import java.lang.reflect.Type; |
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
import java.util.function.Function; |
||||
|
||||
/** |
||||
* Bean的值提供者 |
||||
* |
||||
* @author looly |
||||
*/ |
||||
public class BeanValueProvider implements ValueProvider<String> { |
||||
|
||||
private final Object source; |
||||
private final boolean ignoreError; |
||||
final Map<String, PropDesc> sourcePdMap; |
||||
|
||||
/** |
||||
* 构造 |
||||
* |
||||
* @param bean Bean |
||||
* @param ignoreCase 是否忽略字段大小写 |
||||
* @param ignoreError 是否忽略字段值读取错误 |
||||
*/ |
||||
public BeanValueProvider(Object bean, boolean ignoreCase, boolean ignoreError) { |
||||
this(bean, ignoreCase, ignoreError, null); |
||||
} |
||||
|
||||
/** |
||||
* 构造 |
||||
* |
||||
* @param bean Bean |
||||
* @param ignoreCase 是否忽略字段大小写 |
||||
* @param ignoreError 是否忽略字段值读取错误 |
||||
* @param keyEditor 键编辑器 |
||||
*/ |
||||
public BeanValueProvider(Object bean, boolean ignoreCase, boolean ignoreError, Editor<String> keyEditor) { |
||||
this.source = bean; |
||||
this.ignoreError = ignoreError; |
||||
final Map<String, PropDesc> sourcePdMap = BeanUtil.getBeanDesc(source.getClass()).getPropMap(ignoreCase); |
||||
// issue#2202@Github
|
||||
// 如果用户定义了键编辑器,则提供的map中的数据必须全部转换key
|
||||
// issue#I5VRHW@Gitee 使Function可以被序列化
|
||||
this.sourcePdMap = new FuncKeyMap<>(new HashMap<>(sourcePdMap.size(), 1), (Function<Object, String> & Serializable)(key) -> { |
||||
if (ignoreCase && key instanceof CharSequence) { |
||||
key = key.toString().toLowerCase(); |
||||
} |
||||
if (null != keyEditor) { |
||||
key = keyEditor.edit(key.toString()); |
||||
} |
||||
return key.toString(); |
||||
}); |
||||
this.sourcePdMap.putAll(sourcePdMap); |
||||
} |
||||
|
||||
@Override |
||||
public Object value(String key, Type valueType) { |
||||
final PropDesc sourcePd = getPropDesc(key, valueType); |
||||
|
||||
Object result = null; |
||||
if (null != sourcePd) { |
||||
result = sourcePd.getValue(this.source, valueType, this.ignoreError); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
@Override |
||||
public boolean containsKey(String key) { |
||||
final PropDesc sourcePd = getPropDesc(key, null); |
||||
|
||||
// 字段描述不存在或忽略读的情况下,表示不存在
|
||||
return null != sourcePd && sourcePd.isReadable(false); |
||||
} |
||||
|
||||
/** |
||||
* 获得属性描述 |
||||
* |
||||
* @param key 字段名 |
||||
* @param valueType 值类型,用于判断是否为Boolean,可以为null |
||||
* @return 属性描述 |
||||
*/ |
||||
private PropDesc getPropDesc(String key, Type valueType) { |
||||
PropDesc sourcePd = sourcePdMap.get(key); |
||||
if (null == sourcePd && (null == valueType || Boolean.class == valueType || boolean.class == valueType)) { |
||||
//boolean类型字段字段名支持两种方式
|
||||
sourcePd = sourcePdMap.get(StrUtil.upperFirstAndAddPre(key, "is")); |
||||
} |
||||
|
||||
return sourcePd; |
||||
} |
||||
} |
@ -0,0 +1,42 @@ |
||||
package cn.hutool.core.bean.copier.provider; |
||||
|
||||
import cn.hutool.core.bean.DynaBean; |
||||
import cn.hutool.core.bean.copier.ValueProvider; |
||||
import cn.hutool.core.convert.Convert; |
||||
|
||||
import java.lang.reflect.Type; |
||||
|
||||
/** |
||||
* DynaBean值提供者 |
||||
* |
||||
* @author looly |
||||
* @since 5.4.2 |
||||
*/ |
||||
public class DynaBeanValueProvider implements ValueProvider<String> { |
||||
|
||||
private final DynaBean dynaBean; |
||||
private final boolean ignoreError; |
||||
|
||||
/** |
||||
* 构造 |
||||
* |
||||
* @param dynaBean DynaBean |
||||
* @param ignoreError 是否忽略错误 |
||||
*/ |
||||
public DynaBeanValueProvider(DynaBean dynaBean, boolean ignoreError) { |
||||
this.dynaBean = dynaBean; |
||||
this.ignoreError = ignoreError; |
||||
} |
||||
|
||||
@Override |
||||
public Object value(String key, Type valueType) { |
||||
final Object value = dynaBean.get(key); |
||||
return Convert.convertWithCheck(valueType, value, null, this.ignoreError); |
||||
} |
||||
|
||||
@Override |
||||
public boolean containsKey(String key) { |
||||
return dynaBean.containsProp(key); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,7 @@ |
||||
/** |
||||
* Bean值提供者方式封装 |
||||
* |
||||
* @author looly |
||||
* |
||||
*/ |
||||
package cn.hutool.core.bean.copier.provider; |
@ -0,0 +1,7 @@ |
||||
/** |
||||
* Bean相关操作,包括Bean信息描述,Bean路径表达式、动态Bean、Bean工具等 |
||||
* |
||||
* @author looly |
||||
* |
||||
*/ |
||||
package cn.hutool.core.bean; |
@ -0,0 +1,19 @@ |
||||
package cn.hutool.core.builder; |
||||
|
||||
import java.io.Serializable; |
||||
|
||||
/** |
||||
* 建造者模式接口定义 |
||||
* |
||||
* @param <T> 建造对象类型 |
||||
* @author Looly |
||||
* @since 4.2.2 |
||||
*/ |
||||
public interface Builder<T> extends Serializable{ |
||||
/** |
||||
* 构建 |
||||
* |
||||
* @return 被构建的对象 |
||||
*/ |
||||
T build(); |
||||
} |
@ -0,0 +1,975 @@ |
||||
package cn.hutool.core.builder; |
||||
|
||||
import cn.hutool.core.util.ArrayUtil; |
||||
|
||||
import java.lang.reflect.AccessibleObject; |
||||
import java.lang.reflect.Field; |
||||
import java.lang.reflect.Modifier; |
||||
import java.util.Collection; |
||||
import java.util.Comparator; |
||||
|
||||
/** |
||||
* 用于构建 {@link Comparable#compareTo(Object)} 方法的辅助工具 |
||||
* |
||||
* <p> |
||||
* 在Bean对象中,所有相关字段都参与比对,继承的字段不参与。使用方法如下: |
||||
* |
||||
* <pre> |
||||
* public class MyClass { |
||||
* String field1; |
||||
* int field2; |
||||
* boolean field3; |
||||
* |
||||
* ... |
||||
* |
||||
* public int compareTo(Object o) { |
||||
* MyClass myClass = (MyClass) o; |
||||
* return new CompareToBuilder() |
||||
* .appendSuper(super.compareTo(o) |
||||
* .append(this.field1, myClass.field1) |
||||
* .append(this.field2, myClass.field2) |
||||
* .append(this.field3, myClass.field3) |
||||
* .toComparison(); |
||||
* } |
||||
* } |
||||
* </pre> |
||||
* |
||||
* 字段值按照顺序比较,如果某个字段返回非0结果,比较终止,使用{@code toComparison()}返回结果,后续比较忽略。 |
||||
* |
||||
* <p> |
||||
* 也可以使用{@link #reflectionCompare(Object, Object) reflectionCompare} 方法通过反射比较字段,使用方法如下: |
||||
* |
||||
* <pre> |
||||
* public int compareTo(Object o) { |
||||
* return CompareToBuilder.reflectionCompare(this, o); |
||||
* } |
||||
* </pre> |
||||
* |
||||
*TODO 待整理 |
||||
* 来自于Apache-Commons-Lang3 |
||||
* @author looly,Apache-Commons |
||||
* @since 4.2.2 |
||||
*/ |
||||
public class CompareToBuilder implements Builder<Integer> { |
||||
private static final long serialVersionUID = 1L; |
||||
|
||||
/** 当前比较状态 */ |
||||
private int comparison; |
||||
|
||||
/** |
||||
* 构造,构造后调用append方法增加比较项,然后调用{@link #toComparison()}获取结果 |
||||
*/ |
||||
public CompareToBuilder() { |
||||
comparison = 0; |
||||
} |
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/** |
||||
* 通过反射比较两个Bean对象,对象字段可以为private。比较规则如下: |
||||
* |
||||
* <ul> |
||||
* <li>static字段不比较</li> |
||||
* <li>Transient字段不参与比较</li> |
||||
* <li>父类字段参与比较</li> |
||||
* </ul> |
||||
* |
||||
*<p> |
||||
*如果被比较的两个对象都为<code>null</code>,被认为相同。 |
||||
* |
||||
* @param lhs 第一个对象 |
||||
* @param rhs 第二个对象 |
||||
* @return a negative integer, zero, or a positive integer as <code>lhs</code> |
||||
* is less than, equal to, or greater than <code>rhs</code> |
||||
* @throws NullPointerException if either (but not both) parameters are |
||||
* <code>null</code> |
||||
* @throws ClassCastException if <code>rhs</code> is not assignment-compatible |
||||
* with <code>lhs</code> |
||||
*/ |
||||
public static int reflectionCompare(final Object lhs, final Object rhs) { |
||||
return reflectionCompare(lhs, rhs, false, null); |
||||
} |
||||
|
||||
/** |
||||
* <p>Compares two <code>Object</code>s via reflection.</p> |
||||
* |
||||
* <p>Fields can be private, thus <code>AccessibleObject.setAccessible</code> |
||||
* is used to bypass normal access control checks. This will fail under a |
||||
* security manager unless the appropriate permissions are set.</p> |
||||
* |
||||
* <ul> |
||||
* <li>Static fields will not be compared</li> |
||||
* <li>If <code>compareTransients</code> is <code>true</code>, |
||||
* compares transient members. Otherwise ignores them, as they |
||||
* are likely derived fields.</li> |
||||
* <li>Superclass fields will be compared</li> |
||||
* </ul> |
||||
* |
||||
* <p>If both <code>lhs</code> and <code>rhs</code> are <code>null</code>, |
||||
* they are considered equal.</p> |
||||
* |
||||
* @param lhs left-hand object |
||||
* @param rhs right-hand object |
||||
* @param compareTransients whether to compare transient fields |
||||
* @return a negative integer, zero, or a positive integer as <code>lhs</code> |
||||
* is less than, equal to, or greater than <code>rhs</code> |
||||
* @throws NullPointerException if either <code>lhs</code> or <code>rhs</code> |
||||
* (but not both) is <code>null</code> |
||||
* @throws ClassCastException if <code>rhs</code> is not assignment-compatible |
||||
* with <code>lhs</code> |
||||
*/ |
||||
public static int reflectionCompare(final Object lhs, final Object rhs, final boolean compareTransients) { |
||||
return reflectionCompare(lhs, rhs, compareTransients, null); |
||||
} |
||||
|
||||
/** |
||||
* <p>Compares two <code>Object</code>s via reflection.</p> |
||||
* |
||||
* <p>Fields can be private, thus <code>AccessibleObject.setAccessible</code> |
||||
* is used to bypass normal access control checks. This will fail under a |
||||
* security manager unless the appropriate permissions are set.</p> |
||||
* |
||||
* <ul> |
||||
* <li>Static fields will not be compared</li> |
||||
* <li>If <code>compareTransients</code> is <code>true</code>, |
||||
* compares transient members. Otherwise ignores them, as they |
||||
* are likely derived fields.</li> |
||||
* <li>Superclass fields will be compared</li> |
||||
* </ul> |
||||
* |
||||
* <p>If both <code>lhs</code> and <code>rhs</code> are <code>null</code>, |
||||
* they are considered equal.</p> |
||||
* |
||||
* @param lhs left-hand object |
||||
* @param rhs right-hand object |
||||
* @param excludeFields Collection of String fields to exclude |
||||
* @return a negative integer, zero, or a positive integer as <code>lhs</code> |
||||
* is less than, equal to, or greater than <code>rhs</code> |
||||
* @throws NullPointerException if either <code>lhs</code> or <code>rhs</code> |
||||
* (but not both) is <code>null</code> |
||||
* @throws ClassCastException if <code>rhs</code> is not assignment-compatible |
||||
* with <code>lhs</code> |
||||
* @since 2.2 |
||||
*/ |
||||
public static int reflectionCompare(final Object lhs, final Object rhs, final Collection<String> excludeFields) { |
||||
return reflectionCompare(lhs, rhs, ArrayUtil.toArray(excludeFields, String.class)); |
||||
} |
||||
|
||||
/** |
||||
* <p>Compares two <code>Object</code>s via reflection.</p> |
||||
* |
||||
* <p>Fields can be private, thus <code>AccessibleObject.setAccessible</code> |
||||
* is used to bypass normal access control checks. This will fail under a |
||||
* security manager unless the appropriate permissions are set.</p> |
||||
* |
||||
* <ul> |
||||
* <li>Static fields will not be compared</li> |
||||
* <li>If <code>compareTransients</code> is <code>true</code>, |
||||
* compares transient members. Otherwise ignores them, as they |
||||
* are likely derived fields.</li> |
||||
* <li>Superclass fields will be compared</li> |
||||
* </ul> |
||||
* |
||||
* <p>If both <code>lhs</code> and <code>rhs</code> are <code>null</code>, |
||||
* they are considered equal.</p> |
||||
* |
||||
* @param lhs left-hand object |
||||
* @param rhs right-hand object |
||||
* @param excludeFields array of fields to exclude |
||||
* @return a negative integer, zero, or a positive integer as <code>lhs</code> |
||||
* is less than, equal to, or greater than <code>rhs</code> |
||||
* @throws NullPointerException if either <code>lhs</code> or <code>rhs</code> |
||||
* (but not both) is <code>null</code> |
||||
* @throws ClassCastException if <code>rhs</code> is not assignment-compatible |
||||
* with <code>lhs</code> |
||||
* @since 2.2 |
||||
*/ |
||||
public static int reflectionCompare(final Object lhs, final Object rhs, final String... excludeFields) { |
||||
return reflectionCompare(lhs, rhs, false, null, excludeFields); |
||||
} |
||||
|
||||
/** |
||||
* <p>Compares two <code>Object</code>s via reflection.</p> |
||||
* |
||||
* <p>Fields can be private, thus <code>AccessibleObject.setAccessible</code> |
||||
* is used to bypass normal access control checks. This will fail under a |
||||
* security manager unless the appropriate permissions are set.</p> |
||||
* |
||||
* <ul> |
||||
* <li>Static fields will not be compared</li> |
||||
* <li>If the <code>compareTransients</code> is <code>true</code>, |
||||
* compares transient members. Otherwise ignores them, as they |
||||
* are likely derived fields.</li> |
||||
* <li>Compares superclass fields up to and including <code>reflectUpToClass</code>. |
||||
* If <code>reflectUpToClass</code> is <code>null</code>, compares all superclass fields.</li> |
||||
* </ul> |
||||
* |
||||
* <p>If both <code>lhs</code> and <code>rhs</code> are <code>null</code>, |
||||
* they are considered equal.</p> |
||||
* |
||||
* @param lhs left-hand object |
||||
* @param rhs right-hand object |
||||
* @param compareTransients whether to compare transient fields |
||||
* @param reflectUpToClass last superclass for which fields are compared |
||||
* @param excludeFields fields to exclude |
||||
* @return a negative integer, zero, or a positive integer as <code>lhs</code> |
||||
* is less than, equal to, or greater than <code>rhs</code> |
||||
* @throws NullPointerException if either <code>lhs</code> or <code>rhs</code> |
||||
* (but not both) is <code>null</code> |
||||
* @throws ClassCastException if <code>rhs</code> is not assignment-compatible |
||||
* with <code>lhs</code> |
||||
* @since 2.2 (2.0 as <code>reflectionCompare(Object, Object, boolean, Class)</code>) |
||||
*/ |
||||
public static int reflectionCompare( |
||||
final Object lhs, |
||||
final Object rhs, |
||||
final boolean compareTransients, |
||||
final Class<?> reflectUpToClass, |
||||
final String... excludeFields) { |
||||
|
||||
if (lhs == rhs) { |
||||
return 0; |
||||
} |
||||
if (lhs == null || rhs == null) { |
||||
throw new NullPointerException(); |
||||
} |
||||
Class<?> lhsClazz = lhs.getClass(); |
||||
if (!lhsClazz.isInstance(rhs)) { |
||||
throw new ClassCastException(); |
||||
} |
||||
final CompareToBuilder compareToBuilder = new CompareToBuilder(); |
||||
reflectionAppend(lhs, rhs, lhsClazz, compareToBuilder, compareTransients, excludeFields); |
||||
while (lhsClazz.getSuperclass() != null && lhsClazz != reflectUpToClass) { |
||||
lhsClazz = lhsClazz.getSuperclass(); |
||||
reflectionAppend(lhs, rhs, lhsClazz, compareToBuilder, compareTransients, excludeFields); |
||||
} |
||||
return compareToBuilder.toComparison(); |
||||
} |
||||
|
||||
/** |
||||
* <p>Appends to <code>builder</code> the comparison of <code>lhs</code> |
||||
* to <code>rhs</code> using the fields defined in <code>clazz</code>.</p> |
||||
* |
||||
* @param lhs left-hand object |
||||
* @param rhs right-hand object |
||||
* @param clazz <code>Class</code> that defines fields to be compared |
||||
* @param builder <code>CompareToBuilder</code> to append to |
||||
* @param useTransients whether to compare transient fields |
||||
* @param excludeFields fields to exclude |
||||
*/ |
||||
private static void reflectionAppend( |
||||
final Object lhs, |
||||
final Object rhs, |
||||
final Class<?> clazz, |
||||
final CompareToBuilder builder, |
||||
final boolean useTransients, |
||||
final String[] excludeFields) { |
||||
|
||||
final Field[] fields = clazz.getDeclaredFields(); |
||||
AccessibleObject.setAccessible(fields, true); |
||||
for (int i = 0; i < fields.length && builder.comparison == 0; i++) { |
||||
final Field f = fields[i]; |
||||
if (!ArrayUtil.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 (final 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"); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/** |
||||
* <p>Appends to the <code>builder</code> the <code>compareTo(Object)</code> |
||||
* result of the superclass.</p> |
||||
* |
||||
* @param superCompareTo result of calling <code>super.compareTo(Object)</code> |
||||
* @return this - used to chain append calls |
||||
* @since 2.0 |
||||
*/ |
||||
public CompareToBuilder appendSuper(final int superCompareTo) { |
||||
if (comparison != 0) { |
||||
return this; |
||||
} |
||||
comparison = superCompareTo; |
||||
return this; |
||||
} |
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/** |
||||
* <p>Appends to the <code>builder</code> the comparison of |
||||
* two <code>Object</code>s.</p> |
||||
* |
||||
* <ol> |
||||
* <li>Check if <code>lhs == rhs</code></li> |
||||
* <li>Check if either <code>lhs</code> or <code>rhs</code> is <code>null</code>, |
||||
* a <code>null</code> object is less than a non-<code>null</code> object</li> |
||||
* <li>Check the object contents</li> |
||||
* </ol> |
||||
* |
||||
* <p><code>lhs</code> must either be an array or implement {@link Comparable}.</p> |
||||
* |
||||
* @param lhs left-hand object |
||||
* @param rhs right-hand object |
||||
* @return this - used to chain append calls |
||||
* @throws ClassCastException if <code>rhs</code> is not assignment-compatible |
||||
* with <code>lhs</code> |
||||
*/ |
||||
public CompareToBuilder append(final Object lhs, final Object rhs) { |
||||
return append(lhs, rhs, null); |
||||
} |
||||
|
||||
/** |
||||
* <p>Appends to the <code>builder</code> the comparison of |
||||
* two <code>Object</code>s.</p> |
||||
* |
||||
* <ol> |
||||
* <li>Check if <code>lhs == rhs</code></li> |
||||
* <li>Check if either <code>lhs</code> or <code>rhs</code> is <code>null</code>, |
||||
* a <code>null</code> object is less than a non-<code>null</code> object</li> |
||||
* <li>Check the object contents</li> |
||||
* </ol> |
||||
* |
||||
* <p>If <code>lhs</code> is an array, array comparison methods will be used. |
||||
* Otherwise <code>comparator</code> will be used to compare the objects. |
||||
* If <code>comparator</code> is <code>null</code>, <code>lhs</code> must |
||||
* implement {@link Comparable} instead.</p> |
||||
* |
||||
* @param lhs left-hand object |
||||
* @param rhs right-hand object |
||||
* @param comparator <code>Comparator</code> used to compare the objects, |
||||
* <code>null</code> means treat lhs as <code>Comparable</code> |
||||
* @return this - used to chain append calls |
||||
* @throws ClassCastException if <code>rhs</code> is not assignment-compatible |
||||
* with <code>lhs</code> |
||||
* @since 2.0 |
||||
*/ |
||||
public CompareToBuilder append(final Object lhs, final Object rhs, final Comparator<?> comparator) { |
||||
if (comparison != 0) { |
||||
return this; |
||||
} |
||||
if (lhs == rhs) { |
||||
return this; |
||||
} |
||||
if (lhs == null) { |
||||
comparison = -1; |
||||
return this; |
||||
} |
||||
if (rhs == null) { |
||||
comparison = +1; |
||||
return this; |
||||
} |
||||
if (lhs.getClass().isArray()) { |
||||
// switch on type of array, to dispatch to the correct handler
|
||||
// handles multi dimensional arrays
|
||||
// throws a ClassCastException if rhs is not the correct array type
|
||||
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
|
||||
// throws a ClassCastException if rhs is not an array
|
||||
append((Object[]) lhs, (Object[]) rhs, comparator); |
||||
} |
||||
} else { |
||||
// the simple case, not an array, just test the element
|
||||
if (comparator == null) { |
||||
@SuppressWarnings("unchecked") // assume this can be done; if not throw CCE as per Javadoc
|
||||
final Comparable<Object> comparable = (Comparable<Object>) lhs; |
||||
comparison = comparable.compareTo(rhs); |
||||
} else { |
||||
@SuppressWarnings("unchecked") // assume this can be done; if not throw CCE as per Javadoc
|
||||
final Comparator<Object> comparator2 = (Comparator<Object>) comparator; |
||||
comparison = comparator2.compare(lhs, rhs); |
||||
} |
||||
} |
||||
return this; |
||||
} |
||||
|
||||
//-------------------------------------------------------------------------
|
||||
/** |
||||
* Appends to the <code>builder</code> the comparison of |
||||
* two <code>long</code>s. |
||||
* |
||||
* @param lhs left-hand value |
||||
* @param rhs right-hand value |
||||
* @return this - used to chain append calls |
||||
*/ |
||||
public CompareToBuilder append(final long lhs, final long rhs) { |
||||
if (comparison != 0) { |
||||
return this; |
||||
} |
||||
comparison = (Long.compare(lhs, rhs)); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Appends to the <code>builder</code> the comparison of |
||||
* two <code>int</code>s. |
||||
* |
||||
* @param lhs left-hand value |
||||
* @param rhs right-hand value |
||||
* @return this - used to chain append calls |
||||
*/ |
||||
public CompareToBuilder append(final int lhs, final int rhs) { |
||||
if (comparison != 0) { |
||||
return this; |
||||
} |
||||
comparison = (Integer.compare(lhs, rhs)); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Appends to the <code>builder</code> the comparison of |
||||
* two <code>short</code>s. |
||||
* |
||||
* @param lhs left-hand value |
||||
* @param rhs right-hand value |
||||
* @return this - used to chain append calls |
||||
*/ |
||||
public CompareToBuilder append(final short lhs, final short rhs) { |
||||
if (comparison != 0) { |
||||
return this; |
||||
} |
||||
comparison = (Short.compare(lhs, rhs)); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Appends to the <code>builder</code> the comparison of |
||||
* two <code>char</code>s. |
||||
* |
||||
* @param lhs left-hand value |
||||
* @param rhs right-hand value |
||||
* @return this - used to chain append calls |
||||
*/ |
||||
public CompareToBuilder append(final char lhs, final char rhs) { |
||||
if (comparison != 0) { |
||||
return this; |
||||
} |
||||
comparison = (Character.compare(lhs, rhs)); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Appends to the <code>builder</code> the comparison of |
||||
* two <code>byte</code>s. |
||||
* |
||||
* @param lhs left-hand value |
||||
* @param rhs right-hand value |
||||
* @return this - used to chain append calls |
||||
*/ |
||||
public CompareToBuilder append(final byte lhs, final byte rhs) { |
||||
if (comparison != 0) { |
||||
return this; |
||||
} |
||||
comparison = (Byte.compare(lhs, rhs)); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* <p>Appends to the <code>builder</code> the comparison of |
||||
* two <code>double</code>s.</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 left-hand value |
||||
* @param rhs right-hand value |
||||
* @return this - used to chain append calls |
||||
*/ |
||||
public CompareToBuilder append(final double lhs, final double rhs) { |
||||
if (comparison != 0) { |
||||
return this; |
||||
} |
||||
comparison = Double.compare(lhs, rhs); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* <p>Appends to the <code>builder</code> the comparison of |
||||
* two <code>float</code>s.</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 left-hand value |
||||
* @param rhs right-hand value |
||||
* @return this - used to chain append calls |
||||
*/ |
||||
public CompareToBuilder append(final float lhs, final float rhs) { |
||||
if (comparison != 0) { |
||||
return this; |
||||
} |
||||
comparison = Float.compare(lhs, rhs); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Appends to the <code>builder</code> the comparison of |
||||
* two <code>booleans</code>s. |
||||
* |
||||
* @param lhs left-hand value |
||||
* @param rhs right-hand value |
||||
* @return this - used to chain append calls |
||||
*/ |
||||
public CompareToBuilder append(final boolean lhs, final boolean rhs) { |
||||
if (comparison != 0) { |
||||
return this; |
||||
} |
||||
if (lhs == rhs) { |
||||
return this; |
||||
} |
||||
if (!lhs) { |
||||
comparison = -1; |
||||
} else { |
||||
comparison = +1; |
||||
} |
||||
return this; |
||||
} |
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/** |
||||
* <p>Appends to the <code>builder</code> the deep comparison of |
||||
* two <code>Object</code> arrays.</p> |
||||
* |
||||
* <ol> |
||||
* <li>Check if arrays are the same using <code>==</code></li> |
||||
* <li>Check if for <code>null</code>, <code>null</code> is less than non-<code>null</code></li> |
||||
* <li>Check array length, a short length array is less than a long length array</li> |
||||
* <li>Check array contents element by element using {@link #append(Object, Object, Comparator)}</li> |
||||
* </ol> |
||||
* |
||||
* <p>This method will also will be called for the top level of multi-dimensional, |
||||
* ragged, and multi-typed arrays.</p> |
||||
* |
||||
* @param lhs left-hand array |
||||
* @param rhs right-hand array |
||||
* @return this - used to chain append calls |
||||
* @throws ClassCastException if <code>rhs</code> is not assignment-compatible |
||||
* with <code>lhs</code> |
||||
*/ |
||||
public CompareToBuilder append(final Object[] lhs, final Object[] rhs) { |
||||
return append(lhs, rhs, null); |
||||
} |
||||
|
||||
/** |
||||
* <p>Appends to the <code>builder</code> the deep comparison of |
||||
* two <code>Object</code> arrays.</p> |
||||
* |
||||
* <ol> |
||||
* <li>Check if arrays are the same using <code>==</code></li> |
||||
* <li>Check if for <code>null</code>, <code>null</code> is less than non-<code>null</code></li> |
||||
* <li>Check array length, a short length array is less than a long length array</li> |
||||
* <li>Check array contents element by element using {@link #append(Object, Object, Comparator)}</li> |
||||
* </ol> |
||||
* |
||||
* <p>This method will also will be called for the top level of multi-dimensional, |
||||
* ragged, and multi-typed arrays.</p> |
||||
* |
||||
* @param lhs left-hand array |
||||
* @param rhs right-hand array |
||||
* @param comparator <code>Comparator</code> to use to compare the array elements, |
||||
* <code>null</code> means to treat <code>lhs</code> elements as <code>Comparable</code>. |
||||
* @return this - used to chain append calls |
||||
* @throws ClassCastException if <code>rhs</code> is not assignment-compatible |
||||
* with <code>lhs</code> |
||||
* @since 2.0 |
||||
*/ |
||||
public CompareToBuilder append(final Object[] lhs, final Object[] rhs, final Comparator<?> comparator) { |
||||
if (comparison != 0) { |
||||
return this; |
||||
} |
||||
if (lhs == rhs) { |
||||
return this; |
||||
} |
||||
if (lhs == null) { |
||||
comparison = -1; |
||||
return this; |
||||
} |
||||
if (rhs == null) { |
||||
comparison = +1; |
||||
return this; |
||||
} |
||||
if (lhs.length != rhs.length) { |
||||
comparison = (lhs.length < rhs.length) ? -1 : +1; |
||||
return this; |
||||
} |
||||
for (int i = 0; i < lhs.length && comparison == 0; i++) { |
||||
append(lhs[i], rhs[i], comparator); |
||||
} |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* <p>Appends to the <code>builder</code> the deep comparison of |
||||
* two <code>long</code> arrays.</p> |
||||
* |
||||
* <ol> |
||||
* <li>Check if arrays are the same using <code>==</code></li> |
||||
* <li>Check if for <code>null</code>, <code>null</code> is less than non-<code>null</code></li> |
||||
* <li>Check array length, a shorter length array is less than a longer length array</li> |
||||
* <li>Check array contents element by element using {@link #append(long, long)}</li> |
||||
* </ol> |
||||
* |
||||
* @param lhs left-hand array |
||||
* @param rhs right-hand array |
||||
* @return this - used to chain append calls |
||||
*/ |
||||
public CompareToBuilder append(final long[] lhs, final long[] rhs) { |
||||
if (comparison != 0) { |
||||
return this; |
||||
} |
||||
if (lhs == rhs) { |
||||
return this; |
||||
} |
||||
if (lhs == null) { |
||||
comparison = -1; |
||||
return this; |
||||
} |
||||
if (rhs == null) { |
||||
comparison = +1; |
||||
return this; |
||||
} |
||||
if (lhs.length != rhs.length) { |
||||
comparison = (lhs.length < rhs.length) ? -1 : +1; |
||||
return this; |
||||
} |
||||
for (int i = 0; i < lhs.length && comparison == 0; i++) { |
||||
append(lhs[i], rhs[i]); |
||||
} |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* <p>Appends to the <code>builder</code> the deep comparison of |
||||
* two <code>int</code> arrays.</p> |
||||
* |
||||
* <ol> |
||||
* <li>Check if arrays are the same using <code>==</code></li> |
||||
* <li>Check if for <code>null</code>, <code>null</code> is less than non-<code>null</code></li> |
||||
* <li>Check array length, a shorter length array is less than a longer length array</li> |
||||
* <li>Check array contents element by element using {@link #append(int, int)}</li> |
||||
* </ol> |
||||
* |
||||
* @param lhs left-hand array |
||||
* @param rhs right-hand array |
||||
* @return this - used to chain append calls |
||||
*/ |
||||
public CompareToBuilder append(final int[] lhs, final int[] rhs) { |
||||
if (comparison != 0) { |
||||
return this; |
||||
} |
||||
if (lhs == rhs) { |
||||
return this; |
||||
} |
||||
if (lhs == null) { |
||||
comparison = -1; |
||||
return this; |
||||
} |
||||
if (rhs == null) { |
||||
comparison = +1; |
||||
return this; |
||||
} |
||||
if (lhs.length != rhs.length) { |
||||
comparison = (lhs.length < rhs.length) ? -1 : +1; |
||||
return this; |
||||
} |
||||
for (int i = 0; i < lhs.length && comparison == 0; i++) { |
||||
append(lhs[i], rhs[i]); |
||||
} |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* <p>Appends to the <code>builder</code> the deep comparison of |
||||
* two <code>short</code> arrays.</p> |
||||
* |
||||
* <ol> |
||||
* <li>Check if arrays are the same using <code>==</code></li> |
||||
* <li>Check if for <code>null</code>, <code>null</code> is less than non-<code>null</code></li> |
||||
* <li>Check array length, a shorter length array is less than a longer length array</li> |
||||
* <li>Check array contents element by element using {@link #append(short, short)}</li> |
||||
* </ol> |
||||
* |
||||
* @param lhs left-hand array |
||||
* @param rhs right-hand array |
||||
* @return this - used to chain append calls |
||||
*/ |
||||
public CompareToBuilder append(final short[] lhs, final short[] rhs) { |
||||
if (comparison != 0) { |
||||
return this; |
||||
} |
||||
if (lhs == rhs) { |
||||
return this; |
||||
} |
||||
if (lhs == null) { |
||||
comparison = -1; |
||||
return this; |
||||
} |
||||
if (rhs == null) { |
||||
comparison = +1; |
||||
return this; |
||||
} |
||||
if (lhs.length != rhs.length) { |
||||
comparison = (lhs.length < rhs.length) ? -1 : +1; |
||||
return this; |
||||
} |
||||
for (int i = 0; i < lhs.length && comparison == 0; i++) { |
||||
append(lhs[i], rhs[i]); |
||||
} |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* <p>Appends to the <code>builder</code> the deep comparison of |
||||
* two <code>char</code> arrays.</p> |
||||
* |
||||
* <ol> |
||||
* <li>Check if arrays are the same using <code>==</code></li> |
||||
* <li>Check if for <code>null</code>, <code>null</code> is less than non-<code>null</code></li> |
||||
* <li>Check array length, a shorter length array is less than a longer length array</li> |
||||
* <li>Check array contents element by element using {@link #append(char, char)}</li> |
||||
* </ol> |
||||
* |
||||
* @param lhs left-hand array |
||||
* @param rhs right-hand array |
||||
* @return this - used to chain append calls |
||||
*/ |
||||
public CompareToBuilder append(final char[] lhs, final char[] rhs) { |
||||
if (comparison != 0) { |
||||
return this; |
||||
} |
||||
if (lhs == rhs) { |
||||
return this; |
||||
} |
||||
if (lhs == null) { |
||||
comparison = -1; |
||||
return this; |
||||
} |
||||
if (rhs == null) { |
||||
comparison = +1; |
||||
return this; |
||||
} |
||||
if (lhs.length != rhs.length) { |
||||
comparison = (lhs.length < rhs.length) ? -1 : +1; |
||||
return this; |
||||
} |
||||
for (int i = 0; i < lhs.length && comparison == 0; i++) { |
||||
append(lhs[i], rhs[i]); |
||||
} |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* <p>Appends to the <code>builder</code> the deep comparison of |
||||
* two <code>byte</code> arrays.</p> |
||||
* |
||||
* <ol> |
||||
* <li>Check if arrays are the same using <code>==</code></li> |
||||
* <li>Check if for <code>null</code>, <code>null</code> is less than non-<code>null</code></li> |
||||
* <li>Check array length, a shorter length array is less than a longer length array</li> |
||||
* <li>Check array contents element by element using {@link #append(byte, byte)}</li> |
||||
* </ol> |
||||
* |
||||
* @param lhs left-hand array |
||||
* @param rhs right-hand array |
||||
* @return this - used to chain append calls |
||||
*/ |
||||
public CompareToBuilder append(final byte[] lhs, final byte[] rhs) { |
||||
if (comparison != 0) { |
||||
return this; |
||||
} |
||||
if (lhs == rhs) { |
||||
return this; |
||||
} |
||||
if (lhs == null) { |
||||
comparison = -1; |
||||
return this; |
||||
} |
||||
if (rhs == null) { |
||||
comparison = +1; |
||||
return this; |
||||
} |
||||
if (lhs.length != rhs.length) { |
||||
comparison = (lhs.length < rhs.length) ? -1 : +1; |
||||
return this; |
||||
} |
||||
for (int i = 0; i < lhs.length && comparison == 0; i++) { |
||||
append(lhs[i], rhs[i]); |
||||
} |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* <p>Appends to the <code>builder</code> the deep comparison of |
||||
* two <code>double</code> arrays.</p> |
||||
* |
||||
* <ol> |
||||
* <li>Check if arrays are the same using <code>==</code></li> |
||||
* <li>Check if for <code>null</code>, <code>null</code> is less than non-<code>null</code></li> |
||||
* <li>Check array length, a shorter length array is less than a longer length array</li> |
||||
* <li>Check array contents element by element using {@link #append(double, double)}</li> |
||||
* </ol> |
||||
* |
||||
* @param lhs left-hand array |
||||
* @param rhs right-hand array |
||||
* @return this - used to chain append calls |
||||
*/ |
||||
public CompareToBuilder append(final double[] lhs, final double[] rhs) { |
||||
if (comparison != 0) { |
||||
return this; |
||||
} |
||||
if (lhs == rhs) { |
||||
return this; |
||||
} |
||||
if (lhs == null) { |
||||
comparison = -1; |
||||
return this; |
||||
} |
||||
if (rhs == null) { |
||||
comparison = +1; |
||||
return this; |
||||
} |
||||
if (lhs.length != rhs.length) { |
||||
comparison = (lhs.length < rhs.length) ? -1 : +1; |
||||
return this; |
||||
} |
||||
for (int i = 0; i < lhs.length && comparison == 0; i++) { |
||||
append(lhs[i], rhs[i]); |
||||
} |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* <p>Appends to the <code>builder</code> the deep comparison of |
||||
* two <code>float</code> arrays.</p> |
||||
* |
||||
* <ol> |
||||
* <li>Check if arrays are the same using <code>==</code></li> |
||||
* <li>Check if for <code>null</code>, <code>null</code> is less than non-<code>null</code></li> |
||||
* <li>Check array length, a shorter length array is less than a longer length array</li> |
||||
* <li>Check array contents element by element using {@link #append(float, float)}</li> |
||||
* </ol> |
||||
* |
||||
* @param lhs left-hand array |
||||
* @param rhs right-hand array |
||||
* @return this - used to chain append calls |
||||
*/ |
||||
public CompareToBuilder append(final float[] lhs, final float[] rhs) { |
||||
if (comparison != 0) { |
||||
return this; |
||||
} |
||||
if (lhs == rhs) { |
||||
return this; |
||||
} |
||||
if (lhs == null) { |
||||
comparison = -1; |
||||
return this; |
||||
} |
||||
if (rhs == null) { |
||||
comparison = +1; |
||||
return this; |
||||
} |
||||
if (lhs.length != rhs.length) { |
||||
comparison = (lhs.length < rhs.length) ? -1 : +1; |
||||
return this; |
||||
} |
||||
for (int i = 0; i < lhs.length && comparison == 0; i++) { |
||||
append(lhs[i], rhs[i]); |
||||
} |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* <p>Appends to the <code>builder</code> the deep comparison of |
||||
* two <code>boolean</code> arrays.</p> |
||||
* |
||||
* <ol> |
||||
* <li>Check if arrays are the same using <code>==</code></li> |
||||
* <li>Check if for <code>null</code>, <code>null</code> is less than non-<code>null</code></li> |
||||
* <li>Check array length, a shorter length array is less than a longer length array</li> |
||||
* <li>Check array contents element by element using {@link #append(boolean, boolean)}</li> |
||||
* </ol> |
||||
* |
||||
* @param lhs left-hand array |
||||
* @param rhs right-hand array |
||||
* @return this - used to chain append calls |
||||
*/ |
||||
public CompareToBuilder append(final boolean[] lhs, final boolean[] rhs) { |
||||
if (comparison != 0) { |
||||
return this; |
||||
} |
||||
if (lhs == rhs) { |
||||
return this; |
||||
} |
||||
if (lhs == null) { |
||||
comparison = -1; |
||||
return this; |
||||
} |
||||
if (rhs == null) { |
||||
comparison = +1; |
||||
return this; |
||||
} |
||||
if (lhs.length != rhs.length) { |
||||
comparison = (lhs.length < rhs.length) ? -1 : +1; |
||||
return this; |
||||
} |
||||
for (int i = 0; i < lhs.length && comparison == 0; i++) { |
||||
append(lhs[i], rhs[i]); |
||||
} |
||||
return this; |
||||
} |
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/** |
||||
* Returns a negative integer, a positive integer, or zero as |
||||
* the <code>builder</code> has judged the "left-hand" side |
||||
* as less than, greater than, or equal to the "right-hand" |
||||
* side. |
||||
* |
||||
* @return final comparison result |
||||
* @see #build() |
||||
*/ |
||||
public int toComparison() { |
||||
return comparison; |
||||
} |
||||
|
||||
/** |
||||
* Returns a negative Integer, a positive Integer, or zero as |
||||
* the <code>builder</code> has judged the "left-hand" side |
||||
* as less than, greater than, or equal to the "right-hand" |
||||
* side. |
||||
* |
||||
* @return final comparison result as an Integer |
||||
* @see #toComparison() |
||||
* @since 3.0 |
||||
*/ |
||||
@Override |
||||
public Integer build() { |
||||
return toComparison(); |
||||
} |
||||
} |
||||
|
@ -0,0 +1,563 @@ |
||||
package cn.hutool.core.builder; |
||||
|
||||
import cn.hutool.core.lang.Pair; |
||||
import cn.hutool.core.util.ArrayUtil; |
||||
|
||||
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; |
||||
|
||||
/** |
||||
* <p>{@link Object#equals(Object)} 方法的构建器</p> |
||||
* |
||||
* <p>两个对象equals必须保证hashCode值相等,hashCode值相等不能保证一定equals</p> |
||||
* |
||||
* <p>使用方法如下:</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> 我们也可以通过反射判断所有字段是否equals:</p> |
||||
* <pre> |
||||
* public boolean equals(Object obj) { |
||||
* return EqualsBuilder.reflectionEquals(this, obj); |
||||
* } |
||||
* </pre> |
||||
* <p> |
||||
* 来自Apache Commons Lang改造 |
||||
*/ |
||||
public class EqualsBuilder implements Builder<Boolean> { |
||||
private static final long serialVersionUID = 1L; |
||||
|
||||
/** |
||||
* <p> |
||||
* A registry of objects used by reflection methods to detect cyclical object references and avoid infinite loops. |
||||
* </p> |
||||
*/ |
||||
private static final ThreadLocal<Set<Pair<IDKey, IDKey>>> REGISTRY = new ThreadLocal<>(); |
||||
|
||||
/** |
||||
* <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} object |
||||
* @param rhs the other object |
||||
* @return the pair |
||||
*/ |
||||
static Pair<IDKey, IDKey> getRegisterPair(final Object lhs, final Object rhs) { |
||||
final IDKey left = new IDKey(lhs); |
||||
final IDKey right = new IDKey(rhs); |
||||
return new Pair<>(left, right); |
||||
} |
||||
|
||||
/** |
||||
* <p> |
||||
* Returns {@code true} 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} object to lookup in registry |
||||
* @param rhs the other object to lookup on registry |
||||
* @return boolean {@code true} if the registry contains the given object. |
||||
* @since 3.0 |
||||
*/ |
||||
static boolean isRegistered(final Object lhs, final Object rhs) { |
||||
final Set<Pair<IDKey, IDKey>> registry = getRegistry(); |
||||
final Pair<IDKey, IDKey> pair = getRegisterPair(lhs, rhs); |
||||
final Pair<IDKey, IDKey> swappedPair = new Pair<>(pair.getKey(), pair.getValue()); |
||||
|
||||
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} object to register |
||||
* @param rhs the other object to register |
||||
*/ |
||||
static void register(final Object lhs, final Object rhs) { |
||||
synchronized (EqualsBuilder.class) { |
||||
if (getRegistry() == null) { |
||||
REGISTRY.set(new HashSet<>()); |
||||
} |
||||
} |
||||
|
||||
final Set<Pair<IDKey, IDKey>> registry = getRegistry(); |
||||
final 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} object to unregister |
||||
* @param rhs the other object to unregister |
||||
* @since 3.0 |
||||
*/ |
||||
static void unregister(final Object lhs, final Object rhs) { |
||||
Set<Pair<IDKey, IDKey>> registry = getRegistry(); |
||||
if (registry != null) { |
||||
final Pair<IDKey, IDKey> pair = getRegisterPair(lhs, rhs); |
||||
registry.remove(pair); |
||||
synchronized (EqualsBuilder.class) { |
||||
//read again
|
||||
registry = getRegistry(); |
||||
if (registry != null && registry.isEmpty()) { |
||||
REGISTRY.remove(); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 是否equals,此值随着构建会变更,默认true |
||||
*/ |
||||
private boolean isEquals = true; |
||||
|
||||
/** |
||||
* 构造,初始状态值为true |
||||
*/ |
||||
public EqualsBuilder() { |
||||
// do nothing for now.
|
||||
} |
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
/** |
||||
* <p>反射检查两个对象是否equals,此方法检查对象及其父对象的属性(包括私有属性)是否equals</p> |
||||
* |
||||
* @param lhs 此对象 |
||||
* @param rhs 另一个对象 |
||||
* @param excludeFields 排除的字段集合,如果有不参与计算equals的字段加入此集合即可 |
||||
* @return 两个对象是否equals,是返回{@code true} |
||||
*/ |
||||
public static boolean reflectionEquals(final Object lhs, final Object rhs, final Collection<String> excludeFields) { |
||||
return reflectionEquals(lhs, rhs, ArrayUtil.toArray(excludeFields, String.class)); |
||||
} |
||||
|
||||
/** |
||||
* <p>反射检查两个对象是否equals,此方法检查对象及其父对象的属性(包括私有属性)是否equals</p> |
||||
* |
||||
* @param lhs 此对象 |
||||
* @param rhs 另一个对象 |
||||
* @param excludeFields 排除的字段集合,如果有不参与计算equals的字段加入此集合即可 |
||||
* @return 两个对象是否equals,是返回{@code true} |
||||
*/ |
||||
public static boolean reflectionEquals(final Object lhs, final Object rhs, final String... excludeFields) { |
||||
return reflectionEquals(lhs, rhs, false, null, excludeFields); |
||||
} |
||||
|
||||
/** |
||||
* <p>This method uses reflection to determine if the two {@code Object}s |
||||
* are equal.</p> |
||||
* |
||||
* <p>It uses {@code AccessibleObject.setAccessible} 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. Non-primitive fields are compared using |
||||
* {@code equals()}.</p> |
||||
* |
||||
* <p>If the TestTransients parameter is set to {@code true}, 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}.</p> |
||||
* |
||||
* <p>Static fields will not be tested. Superclass fields will be included.</p> |
||||
* |
||||
* @param lhs {@code this} object |
||||
* @param rhs the other object |
||||
* @param testTransients whether to include transient fields |
||||
* @return {@code true} if the two Objects have tested equals. |
||||
*/ |
||||
public static boolean reflectionEquals(final Object lhs, final Object rhs, final boolean testTransients) { |
||||
return reflectionEquals(lhs, rhs, testTransients, null); |
||||
} |
||||
|
||||
/** |
||||
* <p>This method uses reflection to determine if the two {@code Object}s |
||||
* are equal.</p> |
||||
* |
||||
* <p>It uses {@code AccessibleObject.setAccessible} 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. Non-primitive fields are compared using |
||||
* {@code equals()}.</p> |
||||
* |
||||
* <p>If the testTransients parameter is set to {@code true}, 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}.</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} 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} |
||||
* @param excludeFields array of field names to exclude from testing |
||||
* @return {@code true} if the two Objects have tested equals. |
||||
* @since 2.0 |
||||
*/ |
||||
public static boolean reflectionEquals(final Object lhs, final Object rhs, final boolean testTransients, final Class<?> reflectUpToClass, |
||||
final 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.
|
||||
final Class<?> lhsClass = lhs.getClass(); |
||||
final 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; |
||||
} |
||||
final EqualsBuilder equalsBuilder = new EqualsBuilder(); |
||||
try { |
||||
if (testClass.isArray()) { |
||||
equalsBuilder.append(lhs, rhs); |
||||
} else { |
||||
reflectionAppend(lhs, rhs, testClass, equalsBuilder, testTransients, excludeFields); |
||||
while (testClass.getSuperclass() != null && testClass != reflectUpToClass) { |
||||
testClass = testClass.getSuperclass(); |
||||
reflectionAppend(lhs, rhs, testClass, equalsBuilder, testTransients, excludeFields); |
||||
} |
||||
} |
||||
} catch (final 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( |
||||
final Object lhs, |
||||
final Object rhs, |
||||
final Class<?> clazz, |
||||
final EqualsBuilder builder, |
||||
final boolean useTransients, |
||||
final String[] excludeFields) { |
||||
|
||||
if (isRegistered(lhs, rhs)) { |
||||
return; |
||||
} |
||||
|
||||
try { |
||||
register(lhs, rhs); |
||||
final Field[] fields = clazz.getDeclaredFields(); |
||||
AccessibleObject.setAccessible(fields, true); |
||||
for (int i = 0; i < fields.length && builder.isEquals; i++) { |
||||
final Field f = fields[i]; |
||||
if (!ArrayUtil.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 (final 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()} to this builder.</p> |
||||
* |
||||
* @param superEquals the result of calling {@code super.equals()} |
||||
* @return EqualsBuilder - used to chain calls. |
||||
* @since 2.0 |
||||
*/ |
||||
public EqualsBuilder appendSuper(final boolean superEquals) { |
||||
if (!isEquals) { |
||||
return this; |
||||
} |
||||
isEquals = superEquals; |
||||
return this; |
||||
} |
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
/** |
||||
* <p>Test if two {@code Object}s are equal using their |
||||
* {@code equals} method.</p> |
||||
* |
||||
* @param lhs the left hand object |
||||
* @param rhs the right hand object |
||||
* @return EqualsBuilder - used to chain calls. |
||||
*/ |
||||
public EqualsBuilder append(final Object lhs, final Object rhs) { |
||||
if (!isEquals) { |
||||
return this; |
||||
} |
||||
if (lhs == rhs) { |
||||
return this; |
||||
} |
||||
if (lhs == null || rhs == null) { |
||||
return setEquals(false); |
||||
} |
||||
if (ArrayUtil.isArray(lhs)) { |
||||
// 判断数组的equals
|
||||
return setEquals(ArrayUtil.equals(lhs, rhs)); |
||||
} |
||||
|
||||
// The simple case, not an array, just test the element
|
||||
return setEquals(lhs.equals(rhs)); |
||||
} |
||||
|
||||
/** |
||||
* <p> |
||||
* Test if two {@code long} s are equal. |
||||
* </p> |
||||
* |
||||
* @param lhs the left hand {@code long} |
||||
* @param rhs the right hand {@code long} |
||||
* @return EqualsBuilder - used to chain calls. |
||||
*/ |
||||
public EqualsBuilder append(final long lhs, final long rhs) { |
||||
if (!isEquals) { |
||||
return this; |
||||
} |
||||
isEquals = (lhs == rhs); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* <p>Test if two {@code int}s are equal.</p> |
||||
* |
||||
* @param lhs the left hand {@code int} |
||||
* @param rhs the right hand {@code int} |
||||
* @return EqualsBuilder - used to chain calls. |
||||
*/ |
||||
public EqualsBuilder append(final int lhs, final int rhs) { |
||||
if (!isEquals) { |
||||
return this; |
||||
} |
||||
isEquals = (lhs == rhs); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* <p>Test if two {@code short}s are equal.</p> |
||||
* |
||||
* @param lhs the left hand {@code short} |
||||
* @param rhs the right hand {@code short} |
||||
* @return EqualsBuilder - used to chain calls. |
||||
*/ |
||||
public EqualsBuilder append(final short lhs, final short rhs) { |
||||
if (!isEquals) { |
||||
return this; |
||||
} |
||||
isEquals = (lhs == rhs); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* <p>Test if two {@code char}s are equal.</p> |
||||
* |
||||
* @param lhs the left hand {@code char} |
||||
* @param rhs the right hand {@code char} |
||||
* @return EqualsBuilder - used to chain calls. |
||||
*/ |
||||
public EqualsBuilder append(final char lhs, final char rhs) { |
||||
if (!isEquals) { |
||||
return this; |
||||
} |
||||
isEquals = (lhs == rhs); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* <p>Test if two {@code byte}s are equal.</p> |
||||
* |
||||
* @param lhs the left hand {@code byte} |
||||
* @param rhs the right hand {@code byte} |
||||
* @return EqualsBuilder - used to chain calls. |
||||
*/ |
||||
public EqualsBuilder append(final byte lhs, final byte rhs) { |
||||
if (!isEquals) { |
||||
return this; |
||||
} |
||||
isEquals = (lhs == rhs); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* <p>Test if two {@code double}s are equal by testing that the |
||||
* pattern of bits returned by {@code doubleToLong} are equal.</p> |
||||
* |
||||
* <p>This handles NaNs, Infinities, and {@code -0.0}.</p> |
||||
* |
||||
* <p>It is compatible with the hash code generated by |
||||
* {@code HashCodeBuilder}.</p> |
||||
* |
||||
* @param lhs the left hand {@code double} |
||||
* @param rhs the right hand {@code double} |
||||
* @return EqualsBuilder - used to chain calls. |
||||
*/ |
||||
public EqualsBuilder append(final double lhs, final double rhs) { |
||||
if (!isEquals) { |
||||
return this; |
||||
} |
||||
return append(Double.doubleToLongBits(lhs), Double.doubleToLongBits(rhs)); |
||||
} |
||||
|
||||
/** |
||||
* <p>Test if two {@code float}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}.</p> |
||||
* |
||||
* <p>It is compatible with the hash code generated by |
||||
* {@code HashCodeBuilder}.</p> |
||||
* |
||||
* @param lhs the left hand {@code float} |
||||
* @param rhs the right hand {@code float} |
||||
* @return EqualsBuilder - used to chain calls. |
||||
*/ |
||||
public EqualsBuilder append(final float lhs, final float rhs) { |
||||
if (!isEquals) { |
||||
return this; |
||||
} |
||||
return append(Float.floatToIntBits(lhs), Float.floatToIntBits(rhs)); |
||||
} |
||||
|
||||
/** |
||||
* <p>Test if two {@code booleans}s are equal.</p> |
||||
* |
||||
* @param lhs the left hand {@code boolean} |
||||
* @param rhs the right hand {@code boolean} |
||||
* @return EqualsBuilder - used to chain calls. |
||||
*/ |
||||
public EqualsBuilder append(final boolean lhs, final boolean rhs) { |
||||
if (!isEquals) { |
||||
return this; |
||||
} |
||||
isEquals = (lhs == rhs); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* <p>Returns {@code true} if the fields that have been checked |
||||
* are all equal.</p> |
||||
* |
||||
* @return boolean |
||||
*/ |
||||
public boolean isEquals() { |
||||
return this.isEquals; |
||||
} |
||||
|
||||
/** |
||||
* <p>Returns {@code true} if the fields that have been checked |
||||
* are all equal.</p> |
||||
* |
||||
* @return {@code true} if all of the fields that have been checked |
||||
* are equal, {@code false} otherwise. |
||||
* @since 3.0 |
||||
*/ |
||||
@Override |
||||
public Boolean build() { |
||||
return isEquals(); |
||||
} |
||||
|
||||
/** |
||||
* Sets the {@code isEquals} value. |
||||
* |
||||
* @param isEquals The value to set. |
||||
* @return this |
||||
*/ |
||||
protected EqualsBuilder setEquals(boolean isEquals) { |
||||
this.isEquals = isEquals; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Reset the EqualsBuilder so you can use the same object again |
||||
* |
||||
* @since 2.5 |
||||
*/ |
||||
public void reset() { |
||||
this.isEquals = true; |
||||
} |
||||
} |
@ -0,0 +1,235 @@ |
||||
package cn.hutool.core.builder; |
||||
|
||||
import cn.hutool.core.lang.func.Consumer3; |
||||
import cn.hutool.core.lang.func.Supplier1; |
||||
import cn.hutool.core.lang.func.Supplier2; |
||||
import cn.hutool.core.lang.func.Supplier3; |
||||
import cn.hutool.core.lang.func.Supplier4; |
||||
import cn.hutool.core.lang.func.Supplier5; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
import java.util.function.BiConsumer; |
||||
import java.util.function.Consumer; |
||||
import java.util.function.Supplier; |
||||
|
||||
/** |
||||
* <p>通用Builder</p> |
||||
* 参考: <a href="https://blog.csdn.net/weixin_43935907/article/details/105003719">一看就会的java8通用Builder</a> |
||||
* <p>使用方法如下:</p> |
||||
* <pre> |
||||
* Box box = GenericBuilder |
||||
* .of(Box::new) |
||||
* .with(Box::setId, 1024L) |
||||
* .with(Box::setTitle, "Hello World!") |
||||
* .with(Box::setLength, 9) |
||||
* .with(Box::setWidth, 8) |
||||
* .with(Box::setHeight, 7) |
||||
* .build(); |
||||
* |
||||
* </pre> |
||||
* |
||||
* <p> 我们也可以对已创建的对象进行修改:</p> |
||||
* <pre> |
||||
* Box boxModified = GenericBuilder |
||||
* .of(() -> box) |
||||
* .with(Box::setTitle, "Hello Friend!") |
||||
* .with(Box::setLength, 3) |
||||
* .with(Box::setWidth, 4) |
||||
* .with(Box::setHeight, 5) |
||||
* .build(); |
||||
* </pre> |
||||
* <p> 我们还可以对这样调用有参构造,这对于创建一些在有参构造中包含初始化函数的对象是有意义的:</p> |
||||
* <pre> |
||||
* Box box1 = GenericBuilder |
||||
* .of(Box::new, 2048L, "Hello Partner!", 222, 333, 444) |
||||
* .with(Box::alis) |
||||
* .build(); |
||||
* </pre> |
||||
* <p> 还可能这样构建Map对象:</p> |
||||
* {@code |
||||
* HashMap<String, String> colorMap = GenericBuilder |
||||
* .of(HashMap<String,String>::new) |
||||
* .with(Map::put, "red", "#FF0000") |
||||
* .with(Map::put, "yellow", "#FFFF00") |
||||
* .with(Map::put, "blue", "#0000FF") |
||||
* .build(); |
||||
* } |
||||
* |
||||
* <p>注意:本工具类支持调用的构造方法的参数数量不超过5个,一般方法的参数数量不超过2个,更多的参数不利于阅读和维护。</p> |
||||
* |
||||
* @author TomXin |
||||
* @since 5.7.21 |
||||
*/ |
||||
public class GenericBuilder<T> implements Builder<T> { |
||||
private static final long serialVersionUID = 1L; |
||||
|
||||
/** |
||||
* 实例化器 |
||||
*/ |
||||
private final Supplier<T> instant; |
||||
|
||||
/** |
||||
* 修改器列表 |
||||
*/ |
||||
private final List<Consumer<T>> modifiers = new ArrayList<>(); |
||||
|
||||
/** |
||||
* 构造 |
||||
* |
||||
* @param instant 实例化器 |
||||
*/ |
||||
public GenericBuilder(Supplier<T> instant) { |
||||
this.instant = instant; |
||||
} |
||||
|
||||
/** |
||||
* 通过无参数实例化器创建GenericBuilder |
||||
* |
||||
* @param instant 实例化器 |
||||
* @param <T> 目标类型 |
||||
* @return GenericBuilder对象 |
||||
*/ |
||||
public static <T> GenericBuilder<T> of(Supplier<T> instant) { |
||||
return new GenericBuilder<>(instant); |
||||
} |
||||
|
||||
/** |
||||
* 通过1参数实例化器创建GenericBuilder |
||||
* |
||||
* @param instant 实例化器 |
||||
* @param p1 参数一 |
||||
* @param <T> 目标类型 |
||||
* @param <P1> 参数一类型 |
||||
* @return GenericBuilder对象 |
||||
*/ |
||||
public static <T, P1> GenericBuilder<T> of(Supplier1<T, P1> instant, P1 p1) { |
||||
return of(instant.toSupplier(p1)); |
||||
} |
||||
|
||||
/** |
||||
* 通过2参数实例化器创建GenericBuilder |
||||
* |
||||
* @param instant 实例化器 |
||||
* @param p1 参数一 |
||||
* @param p2 参数二 |
||||
* @param <T> 目标类型 |
||||
* @param <P1> 参数一类型 |
||||
* @param <P2> 参数二类型 |
||||
* @return GenericBuilder对象 |
||||
*/ |
||||
public static <T, P1, P2> GenericBuilder<T> of(Supplier2<T, P1, P2> instant, P1 p1, P2 p2) { |
||||
return of(instant.toSupplier(p1, p2)); |
||||
} |
||||
|
||||
/** |
||||
* 通过3参数实例化器创建GenericBuilder |
||||
* |
||||
* @param instant 实例化器 |
||||
* @param p1 参数一 |
||||
* @param p2 参数二 |
||||
* @param p3 参数三 |
||||
* @param <T> 目标类型 |
||||
* @param <P1> 参数一类型 |
||||
* @param <P2> 参数二类型 |
||||
* @param <P3> 参数三类型 |
||||
* @return GenericBuilder对象 |
||||
*/ |
||||
public static <T, P1, P2, P3> GenericBuilder<T> of(Supplier3<T, P1, P2, P3> instant, P1 p1, P2 p2, P3 p3) { |
||||
return of(instant.toSupplier(p1, p2, p3)); |
||||
} |
||||
|
||||
/** |
||||
* 通过4参数实例化器创建GenericBuilder |
||||
* |
||||
* @param instant 实例化器 |
||||
* @param p1 参数一 |
||||
* @param p2 参数二 |
||||
* @param p3 参数三 |
||||
* @param p4 参数四 |
||||
* @param <T> 目标类型 |
||||
* @param <P1> 参数一类型 |
||||
* @param <P2> 参数二类型 |
||||
* @param <P3> 参数三类型 |
||||
* @param <P4> 参数四类型 |
||||
* @return GenericBuilder对象 |
||||
*/ |
||||
public static <T, P1, P2, P3, P4> GenericBuilder<T> of(Supplier4<T, P1, P2, P3, P4> instant, P1 p1, P2 p2, P3 p3, P4 p4) { |
||||
return of(instant.toSupplier(p1, p2, p3, p4)); |
||||
} |
||||
|
||||
/** |
||||
* 通过5参数实例化器创建GenericBuilder |
||||
* |
||||
* @param instant 实例化器 |
||||
* @param p1 参数一 |
||||
* @param p2 参数二 |
||||
* @param p3 参数三 |
||||
* @param p4 参数四 |
||||
* @param p5 参数五 |
||||
* @param <T> 目标类型 |
||||
* @param <P1> 参数一类型 |
||||
* @param <P2> 参数二类型 |
||||
* @param <P3> 参数三类型 |
||||
* @param <P4> 参数四类型 |
||||
* @param <P5> 参数五类型 |
||||
* @return GenericBuilder对象 |
||||
*/ |
||||
public static <T, P1, P2, P3, P4, P5> GenericBuilder<T> of(Supplier5<T, P1, P2, P3, P4, P5> instant, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5) { |
||||
return of(instant.toSupplier(p1, p2, p3, p4, p5)); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 调用无参数方法 |
||||
* |
||||
* @param consumer 无参数Consumer |
||||
* @return GenericBuilder对象 |
||||
*/ |
||||
public GenericBuilder<T> with(Consumer<T> consumer) { |
||||
modifiers.add(consumer); |
||||
return this; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 调用1参数方法 |
||||
* |
||||
* @param consumer 1参数Consumer |
||||
* @param p1 参数一 |
||||
* @param <P1> 参数一类型 |
||||
* @return GenericBuilder对象 |
||||
*/ |
||||
public <P1> GenericBuilder<T> with(BiConsumer<T, P1> consumer, P1 p1) { |
||||
modifiers.add(instant -> consumer.accept(instant, p1)); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 调用2参数方法 |
||||
* |
||||
* @param consumer 2参数Consumer |
||||
* @param p1 参数一 |
||||
* @param p2 参数二 |
||||
* @param <P1> 参数一类型 |
||||
* @param <P2> 参数二类型 |
||||
* @return GenericBuilder对象 |
||||
*/ |
||||
public <P1, P2> GenericBuilder<T> with(Consumer3<T, P1, P2> consumer, P1 p1, P2 p2) { |
||||
modifiers.add(instant -> consumer.accept(instant, p1, p2)); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 构建 |
||||
* |
||||
* @return 目标对象 |
||||
*/ |
||||
@Override |
||||
public T build() { |
||||
T value = instant.get(); |
||||
modifiers.forEach(modifier -> modifier.accept(value)); |
||||
modifiers.clear(); |
||||
return value; |
||||
} |
||||
} |
@ -0,0 +1,958 @@ |
||||
package cn.hutool.core.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 cn.hutool.core.lang.Assert; |
||||
import cn.hutool.core.util.ArrayUtil; |
||||
|
||||
/** |
||||
* <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://www.oracle.com/technetwork/java/effectivejava-136174.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> |
||||
* |
||||
* TODO 待整理 |
||||
* 来自于Apache-Commons-Lang3 |
||||
* @author looly,Apache-Commons |
||||
* @since 4.2.2 |
||||
*/ |
||||
public class HashCodeBuilder implements Builder<Integer> { |
||||
private static final long serialVersionUID = 1L; |
||||
|
||||
/** |
||||
* The default initial value to use in reflection hash code building. |
||||
*/ |
||||
private static final int DEFAULT_INITIAL_VALUE = 17; |
||||
|
||||
/** |
||||
* The default multipler value to use in reflection hash code building. |
||||
*/ |
||||
private static final int DEFAULT_MULTIPLIER_VALUE = 37; |
||||
|
||||
/** |
||||
* <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<>(); |
||||
|
||||
/* |
||||
* 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 |
||||
*/ |
||||
private 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 |
||||
*/ |
||||
private static boolean isRegistered(final Object value) { |
||||
final 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(final Object object, final Class<?> clazz, final HashCodeBuilder builder, final boolean useTransients, |
||||
final String[] excludeFields) { |
||||
if (isRegistered(object)) { |
||||
return; |
||||
} |
||||
try { |
||||
register(object); |
||||
final Field[] fields = clazz.getDeclaredFields(); |
||||
AccessibleObject.setAccessible(fields, true); |
||||
for (final Field field : fields) { |
||||
if (!ArrayUtil.contains(excludeFields, field.getName()) |
||||
&& (field.getName().indexOf('$') == -1) |
||||
&& (useTransients || !Modifier.isTransient(field.getModifiers())) |
||||
&& (!Modifier.isStatic(field.getModifiers()))) { |
||||
try { |
||||
final Object fieldValue = field.get(object); |
||||
builder.append(fieldValue); |
||||
} catch (final 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> |
||||
* Uses reflection to build a valid hash code from the fields of {@code object}. |
||||
* </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. This will be the returned |
||||
* value if no fields are found to include in the hash code |
||||
* @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(final int initialNonZeroOddNumber, final int multiplierNonZeroOddNumber, final Object object) { |
||||
return reflectionHashCode(initialNonZeroOddNumber, multiplierNonZeroOddNumber, object, false, null); |
||||
} |
||||
|
||||
/** |
||||
* <p> |
||||
* Uses reflection to build a valid hash code from the fields of {@code object}. |
||||
* </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. This will be the returned |
||||
* value if no fields are found to include in the hash code |
||||
* @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(final int initialNonZeroOddNumber, final int multiplierNonZeroOddNumber, final Object object, |
||||
final boolean testTransients) { |
||||
return reflectionHashCode(initialNonZeroOddNumber, multiplierNonZeroOddNumber, object, testTransients, null); |
||||
} |
||||
|
||||
/** |
||||
* <p> |
||||
* Uses reflection to build a valid hash code from the fields of {@code object}. |
||||
* </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. This will be the returned |
||||
* value if no fields are found to include in the hash code |
||||
* @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(final int initialNonZeroOddNumber, final int multiplierNonZeroOddNumber, final T object, |
||||
final boolean testTransients, final Class<? super T> reflectUpToClass, final String... excludeFields) { |
||||
|
||||
if (object == null) { |
||||
throw new IllegalArgumentException("The object to build a hash code for must not be null"); |
||||
} |
||||
final 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> |
||||
* Uses reflection to build a valid hash code from the fields of {@code object}. |
||||
* </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. If no fields are found to include |
||||
* in the hash code, the result of this method will be constant. |
||||
* </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(final Object object, final boolean testTransients) { |
||||
return reflectionHashCode(DEFAULT_INITIAL_VALUE, DEFAULT_MULTIPLIER_VALUE, object, |
||||
testTransients, null); |
||||
} |
||||
|
||||
/** |
||||
* <p> |
||||
* Uses reflection to build a valid hash code from the fields of {@code object}. |
||||
* </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. If no fields are found to include |
||||
* in the hash code, the result of this method will be constant. |
||||
* </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(final Object object, final Collection<String> excludeFields) { |
||||
return reflectionHashCode(object, ArrayUtil.toArray(excludeFields, String.class)); |
||||
} |
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/** |
||||
* <p> |
||||
* Uses reflection to build a valid hash code from the fields of {@code object}. |
||||
* </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. If no fields are found to include |
||||
* in the hash code, the result of this method will be constant. |
||||
* </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(final Object object, final String... excludeFields) { |
||||
return reflectionHashCode(DEFAULT_INITIAL_VALUE, DEFAULT_MULTIPLIER_VALUE, 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(final 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(final 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; |
||||
|
||||
/** |
||||
* <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, 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 initialOddNumber |
||||
* an odd number used as the initial value |
||||
* @param multiplierOddNumber |
||||
* an odd number used as the multiplier |
||||
* @throws IllegalArgumentException |
||||
* if the number is even |
||||
*/ |
||||
public HashCodeBuilder(final int initialOddNumber, final int multiplierOddNumber) { |
||||
Assert.isTrue(initialOddNumber % 2 != 0, "HashCodeBuilder requires an odd initial value"); |
||||
Assert.isTrue(multiplierOddNumber % 2 != 0, "HashCodeBuilder requires an odd multiplier"); |
||||
iConstant = multiplierOddNumber; |
||||
iTotal = initialOddNumber; |
||||
} |
||||
|
||||
/** |
||||
* <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 <i>Effective Java</i> design. |
||||
* </p> |
||||
* |
||||
* @param value |
||||
* the boolean to add to the <code>hashCode</code> |
||||
* @return this |
||||
*/ |
||||
public HashCodeBuilder append(final 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(final boolean[] array) { |
||||
if (array == null) { |
||||
iTotal = iTotal * iConstant; |
||||
} else { |
||||
for (final 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(final 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(final byte[] array) { |
||||
if (array == null) { |
||||
iTotal = iTotal * iConstant; |
||||
} else { |
||||
for (final 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(final 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(final char[] array) { |
||||
if (array == null) { |
||||
iTotal = iTotal * iConstant; |
||||
} else { |
||||
for (final 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(final 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(final double[] array) { |
||||
if (array == null) { |
||||
iTotal = iTotal * iConstant; |
||||
} else { |
||||
for (final 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(final 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(final float[] array) { |
||||
if (array == null) { |
||||
iTotal = iTotal * iConstant; |
||||
} else { |
||||
for (final 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(final 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(final int[] array) { |
||||
if (array == null) { |
||||
iTotal = iTotal * iConstant; |
||||
} else { |
||||
for (final 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(final 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(final long[] array) { |
||||
if (array == null) { |
||||
iTotal = iTotal * iConstant; |
||||
} else { |
||||
for (final 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(final 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(final Object[] array) { |
||||
if (array == null) { |
||||
iTotal = iTotal * iConstant; |
||||
} else { |
||||
for (final 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(final 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(final short[] array) { |
||||
if (array == null) { |
||||
iTotal = iTotal * iConstant; |
||||
} else { |
||||
for (final 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(final 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 |
||||
*/ |
||||
@Override |
||||
public Integer build() { |
||||
return 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(); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,61 @@ |
||||
package cn.hutool.core.builder; |
||||
|
||||
import java.io.Serializable; |
||||
|
||||
/** |
||||
* 包装唯一键(System.identityHashCode())使对象只有和自己 equals |
||||
* |
||||
* 此对象用于消除小概率下System.identityHashCode()产生的ID重复问题。 |
||||
* |
||||
* 来自于Apache-Commons-Lang3 |
||||
* @author looly,Apache-Commons |
||||
* @since 4.2.2 |
||||
*/ |
||||
final class IDKey implements Serializable{ |
||||
private static final long serialVersionUID = 1L; |
||||
|
||||
private final Object value; |
||||
private final int id; |
||||
|
||||
/** |
||||
* 构造 |
||||
* |
||||
* @param obj 计算唯一ID的对象 |
||||
*/ |
||||
public IDKey(final Object obj) { |
||||
id = System.identityHashCode(obj); |
||||
// 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 = obj; |
||||
} |
||||
|
||||
/** |
||||
* 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(final Object other) { |
||||
if (!(other instanceof IDKey)) { |
||||
return false; |
||||
} |
||||
final IDKey idKey = (IDKey) other; |
||||
if (id != idKey.id) { |
||||
return false; |
||||
} |
||||
// Note that identity equals is used.
|
||||
return value == idKey.value; |
||||
} |
||||
} |
@ -0,0 +1,8 @@ |
||||
/** |
||||
* 建造者工具<br> |
||||
* 用于建造特定对象或结果 |
||||
* |
||||
* @author looly |
||||
* |
||||
*/ |
||||
package cn.hutool.core.builder; |
@ -0,0 +1,32 @@ |
||||
package cn.hutool.core.clone; |
||||
|
||||
import cn.hutool.core.exceptions.ExceptionUtil; |
||||
import cn.hutool.core.util.StrUtil; |
||||
|
||||
/** |
||||
* 克隆异常 |
||||
* @author xiaoleilu |
||||
*/ |
||||
public class CloneRuntimeException extends RuntimeException{ |
||||
private static final long serialVersionUID = 6774837422188798989L; |
||||
|
||||
public CloneRuntimeException(Throwable e) { |
||||
super(ExceptionUtil.getMessage(e), e); |
||||
} |
||||
|
||||
public CloneRuntimeException(String message) { |
||||
super(message); |
||||
} |
||||
|
||||
public CloneRuntimeException(String messageTemplate, Object... params) { |
||||
super(StrUtil.format(messageTemplate, params)); |
||||
} |
||||
|
||||
public CloneRuntimeException(String message, Throwable throwable) { |
||||
super(message, throwable); |
||||
} |
||||
|
||||
public CloneRuntimeException(Throwable throwable, String messageTemplate, Object... params) { |
||||
super(StrUtil.format(messageTemplate, params), throwable); |
||||
} |
||||
} |
@ -0,0 +1,21 @@ |
||||
package cn.hutool.core.clone; |
||||
|
||||
/** |
||||
* 克隆支持类,提供默认的克隆方法 |
||||
* @author Looly |
||||
* |
||||
* @param <T> 继承类的类型 |
||||
*/ |
||||
public class CloneSupport<T> implements Cloneable<T>{ |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
@Override |
||||
public T clone() { |
||||
try { |
||||
return (T) super.clone(); |
||||
} catch (CloneNotSupportedException e) { |
||||
throw new CloneRuntimeException(e); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,16 @@ |
||||
package cn.hutool.core.clone; |
||||
|
||||
/** |
||||
* 克隆支持接口 |
||||
* @author Looly |
||||
* |
||||
* @param <T> 实现克隆接口的类型 |
||||
*/ |
||||
public interface Cloneable<T> extends java.lang.Cloneable{ |
||||
|
||||
/** |
||||
* 克隆当前对象,浅复制 |
||||
* @return 克隆后的对象 |
||||
*/ |
||||
T clone(); |
||||
} |
@ -0,0 +1,28 @@ |
||||
package cn.hutool.core.clone; |
||||
|
||||
|
||||
import cn.hutool.core.util.ReflectUtil; |
||||
|
||||
/** |
||||
* 克隆默认实现接口,用于实现返回指定泛型类型的克隆方法 |
||||
* |
||||
* @param <T> 泛型类型 |
||||
* @since 5.7.17 |
||||
*/ |
||||
public interface DefaultCloneable<T> extends java.lang.Cloneable { |
||||
|
||||
/** |
||||
* 浅拷贝,提供默认的泛型返回值的clone方法。 |
||||
* |
||||
* @return obj |
||||
*/ |
||||
default T clone0() { |
||||
try { |
||||
return ReflectUtil.invoke(this, "clone"); |
||||
} catch (Exception e) { |
||||
throw new CloneRuntimeException(e); |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
@ -0,0 +1,7 @@ |
||||
/** |
||||
* 克隆封装 |
||||
* |
||||
* @author looly |
||||
* |
||||
*/ |
||||
package cn.hutool.core.clone; |
@ -0,0 +1,129 @@ |
||||
package cn.hutool.core.codec; |
||||
|
||||
import cn.hutool.core.lang.Assert; |
||||
|
||||
/** |
||||
* BCD码(Binary-Coded Decimal)亦称二进码十进数或二-十进制代码<br> |
||||
* BCD码这种编码形式利用了四个位元来储存一个十进制的数码,使二进制和十进制之间的转换得以快捷的进行<br> |
||||
* see http://cuisuqiang.iteye.com/blog/1429956
|
||||
* @author Looly |
||||
* |
||||
* @deprecated 由于对于ASCII的编码解码有缺陷,且这种BCD实现并不规范,因此会在6.0.0中移除 |
||||
*/ |
||||
@Deprecated |
||||
public class BCD { |
||||
|
||||
/** |
||||
* 字符串转BCD码 |
||||
* @param asc ASCII字符串 |
||||
* @return BCD |
||||
*/ |
||||
public static byte[] strToBcd(String asc) { |
||||
Assert.notNull(asc, "ASCII must not be null!"); |
||||
int len = asc.length(); |
||||
int mod = len % 2; |
||||
if (mod != 0) { |
||||
asc = "0" + asc; |
||||
len = asc.length(); |
||||
} |
||||
byte[] abt; |
||||
if (len >= 2) { |
||||
len >>= 1; |
||||
} |
||||
byte[] bbt; |
||||
bbt = new byte[len]; |
||||
abt = asc.getBytes(); |
||||
int j; |
||||
int k; |
||||
for (int p = 0; p < asc.length() / 2; p++) { |
||||
if ((abt[2 * p] >= '0') && (abt[2 * p] <= '9')) { |
||||
j = abt[2 * p] - '0'; |
||||
} else if ((abt[2 * p] >= 'a') && (abt[2 * p] <= 'z')) { |
||||
j = abt[2 * p] - 'a' + 0x0a; |
||||
} else { |
||||
j = abt[2 * p] - 'A' + 0x0a; |
||||
} |
||||
if ((abt[2 * p + 1] >= '0') && (abt[2 * p + 1] <= '9')) { |
||||
k = abt[2 * p + 1] - '0'; |
||||
} else if ((abt[2 * p + 1] >= 'a') && (abt[2 * p + 1] <= 'z')) { |
||||
k = abt[2 * p + 1] - 'a' + 0x0a; |
||||
} else { |
||||
k = abt[2 * p + 1] - 'A' + 0x0a; |
||||
} |
||||
int a = (j << 4) + k; |
||||
byte b = (byte) a; |
||||
bbt[p] = b; |
||||
} |
||||
return bbt; |
||||
} |
||||
|
||||
/** |
||||
* ASCII转BCD |
||||
* @param ascii ASCII byte数组 |
||||
* @return BCD |
||||
*/ |
||||
public static byte[] ascToBcd(byte[] ascii) { |
||||
Assert.notNull(ascii, "Ascii must be not null!"); |
||||
return ascToBcd(ascii, ascii.length); |
||||
} |
||||
|
||||
/** |
||||
* ASCII转BCD |
||||
* @param ascii ASCII byte数组 |
||||
* @param ascLength 长度 |
||||
* @return BCD |
||||
*/ |
||||
public static byte[] ascToBcd(byte[] ascii, int ascLength) { |
||||
Assert.notNull(ascii, "Ascii must be not null!"); |
||||
byte[] bcd = new byte[ascLength / 2]; |
||||
int j = 0; |
||||
for (int i = 0; i < (ascLength + 1) / 2; i++) { |
||||
bcd[i] = ascToBcd(ascii[j++]); |
||||
bcd[i] = (byte) (((j >= ascLength) ? 0x00 : ascToBcd(ascii[j++])) + (bcd[i] << 4)); |
||||
} |
||||
return bcd; |
||||
} |
||||
|
||||
/** |
||||
* BCD转ASCII字符串 |
||||
* @param bytes BCD byte数组 |
||||
* @return ASCII字符串 |
||||
*/ |
||||
public static String bcdToStr(byte[] bytes) { |
||||
Assert.notNull(bytes, "Bcd bytes must be not null!"); |
||||
char[] temp = new char[bytes.length * 2]; |
||||
char val; |
||||
|
||||
for (int i = 0; i < bytes.length; i++) { |
||||
val = (char) (((bytes[i] & 0xf0) >> 4) & 0x0f); |
||||
temp[i * 2] = (char) (val > 9 ? val + 'A' - 10 : val + '0'); |
||||
|
||||
val = (char) (bytes[i] & 0x0f); |
||||
temp[i * 2 + 1] = (char) (val > 9 ? val + 'A' - 10 : val + '0'); |
||||
} |
||||
return new String(temp); |
||||
} |
||||
|
||||
|
||||
//----------------------------------------------------------------- Private method start
|
||||
/** |
||||
* 转换单个byte为BCD |
||||
* @param asc ACSII |
||||
* @return BCD |
||||
*/ |
||||
private static byte ascToBcd(byte asc) { |
||||
byte bcd; |
||||
|
||||
if ((asc >= '0') && (asc <= '9')) { |
||||
bcd = (byte) (asc - '0'); |
||||
}else if ((asc >= 'A') && (asc <= 'F')) { |
||||
bcd = (byte) (asc - 'A' + 10); |
||||
}else if ((asc >= 'a') && (asc <= 'f')) { |
||||
bcd = (byte) (asc - 'a' + 10); |
||||
}else { |
||||
bcd = (byte) (asc - 48); |
||||
} |
||||
return bcd; |
||||
} |
||||
//----------------------------------------------------------------- Private method end
|
||||
} |
@ -0,0 +1,118 @@ |
||||
package cn.hutool.core.codec; |
||||
|
||||
import cn.hutool.core.exceptions.UtilException; |
||||
import cn.hutool.core.util.StrUtil; |
||||
|
||||
/** |
||||
* Base16(Hex)编码解码器<br> |
||||
* 十六进制(简写为hex或下标16)在数学中是一种逢16进1的进位制,一般用数字0到9和字母A到F表示(其中:A~F即10~15)。<br> |
||||
* 例如十进制数57,在二进制写作111001,在16进制写作39。 |
||||
* |
||||
* @author looly |
||||
* @since 5.7.23 |
||||
*/ |
||||
public class Base16Codec implements Encoder<byte[], char[]>, Decoder<CharSequence, byte[]> { |
||||
|
||||
public static final Base16Codec CODEC_LOWER = new Base16Codec(true); |
||||
public static final Base16Codec CODEC_UPPER = new Base16Codec(false); |
||||
|
||||
private final char[] alphabets; |
||||
|
||||
/** |
||||
* 构造 |
||||
* |
||||
* @param lowerCase 是否小写 |
||||
*/ |
||||
public Base16Codec(boolean lowerCase) { |
||||
this.alphabets = (lowerCase ? "0123456789abcdef" : "0123456789ABCDEF").toCharArray(); |
||||
} |
||||
|
||||
@Override |
||||
public char[] encode(byte[] data) { |
||||
final int len = data.length; |
||||
final char[] out = new char[len << 1];//len*2
|
||||
// two characters from the hex value.
|
||||
for (int i = 0, j = 0; i < len; i++) { |
||||
out[j++] = alphabets[(0xF0 & data[i]) >>> 4];// 高位
|
||||
out[j++] = alphabets[0x0F & data[i]];// 低位
|
||||
} |
||||
return out; |
||||
} |
||||
|
||||
@Override |
||||
public byte[] decode(CharSequence encoded) { |
||||
if (StrUtil.isEmpty(encoded)) { |
||||
return null; |
||||
} |
||||
|
||||
encoded = StrUtil.cleanBlank(encoded); |
||||
int len = encoded.length(); |
||||
|
||||
if ((len & 0x01) != 0) { |
||||
// 如果提供的数据是奇数长度,则前面补0凑偶数
|
||||
encoded = "0" + encoded; |
||||
len = encoded.length(); |
||||
} |
||||
|
||||
final byte[] out = new byte[len >> 1]; |
||||
|
||||
// two characters form the hex value.
|
||||
for (int i = 0, j = 0; j < len; i++) { |
||||
int f = toDigit(encoded.charAt(j), j) << 4; |
||||
j++; |
||||
f = f | toDigit(encoded.charAt(j), j); |
||||
j++; |
||||
out[i] = (byte) (f & 0xFF); |
||||
} |
||||
|
||||
return out; |
||||
} |
||||
|
||||
/** |
||||
* 将指定char值转换为Unicode字符串形式,常用于特殊字符(例如汉字)转Unicode形式<br> |
||||
* 转换的字符串如果u后不足4位,则前面用0填充,例如: |
||||
* |
||||
* <pre> |
||||
* '你' =》'\u4f60' |
||||
* </pre> |
||||
* |
||||
* @param ch char值 |
||||
* @return Unicode表现形式 |
||||
*/ |
||||
public String toUnicodeHex(char ch) { |
||||
return "\\u" +//
|
||||
alphabets[(ch >> 12) & 15] +//
|
||||
alphabets[(ch >> 8) & 15] +//
|
||||
alphabets[(ch >> 4) & 15] +//
|
||||
alphabets[(ch) & 15]; |
||||
} |
||||
|
||||
/** |
||||
* 将byte值转为16进制并添加到{@link StringBuilder}中 |
||||
* |
||||
* @param builder {@link StringBuilder} |
||||
* @param b byte |
||||
*/ |
||||
public void appendHex(StringBuilder builder, byte b) { |
||||
int high = (b & 0xf0) >>> 4;//高位
|
||||
int low = b & 0x0f;//低位
|
||||
builder.append(alphabets[high]); |
||||
builder.append(alphabets[low]); |
||||
} |
||||
|
||||
/** |
||||
* 将十六进制字符转换成一个整数 |
||||
* |
||||
* @param ch 十六进制char |
||||
* @param index 十六进制字符在字符数组中的位置 |
||||
* @return 一个整数 |
||||
* @throws UtilException 当ch不是一个合法的十六进制字符时,抛出运行时异常 |
||||
*/ |
||||
private static int toDigit(char ch, int index) { |
||||
int digit = Character.digit(ch, 16); |
||||
if (digit < 0) { |
||||
throw new UtilException("Illegal hexadecimal character {} at index {}", ch, index); |
||||
} |
||||
return digit; |
||||
} |
||||
} |
@ -0,0 +1,148 @@ |
||||
package cn.hutool.core.codec; |
||||
|
||||
import cn.hutool.core.util.CharsetUtil; |
||||
import cn.hutool.core.util.StrUtil; |
||||
|
||||
import java.nio.charset.Charset; |
||||
|
||||
/** |
||||
* Base32 - encodes and decodes RFC4648 Base32 (see https://datatracker.ietf.org/doc/html/rfc4648#section-6 )<br>
|
||||
* base32就是用32(2的5次方)个特定ASCII码来表示256个ASCII码。<br> |
||||
* 所以,5个ASCII字符经过base32编码后会变为8个字符(公约数为40),长度增加3/5.不足8n用“=”补足。<br> |
||||
* 根据RFC4648 Base32规范,支持两种模式: |
||||
* <ul> |
||||
* <li>Base 32 Alphabet (ABCDEFGHIJKLMNOPQRSTUVWXYZ234567)</li> |
||||
* <li>"Extended Hex" Base 32 Alphabet (0123456789ABCDEFGHIJKLMNOPQRSTUV)</li> |
||||
* </ul> |
||||
* |
||||
* @author Looly |
||||
*/ |
||||
public class Base32 { |
||||
//----------------------------------------------------------------------------------------- encode
|
||||
|
||||
/** |
||||
* 编码 |
||||
* |
||||
* @param bytes 数据 |
||||
* @return base32 |
||||
*/ |
||||
public static String encode(final byte[] bytes) { |
||||
return Base32Codec.INSTANCE.encode(bytes); |
||||
} |
||||
|
||||
/** |
||||
* base32编码 |
||||
* |
||||
* @param source 被编码的base32字符串 |
||||
* @return 被加密后的字符串 |
||||
*/ |
||||
public static String encode(String source) { |
||||
return encode(source, CharsetUtil.CHARSET_UTF_8); |
||||
} |
||||
|
||||
/** |
||||
* base32编码 |
||||
* |
||||
* @param source 被编码的base32字符串 |
||||
* @param charset 字符集 |
||||
* @return 被加密后的字符串 |
||||
*/ |
||||
public static String encode(String source, Charset charset) { |
||||
return encode(StrUtil.bytes(source, charset)); |
||||
} |
||||
|
||||
/** |
||||
* 编码 |
||||
* |
||||
* @param bytes 数据(Hex模式) |
||||
* @return base32 |
||||
*/ |
||||
public static String encodeHex(final byte[] bytes) { |
||||
return Base32Codec.INSTANCE.encode(bytes, true); |
||||
} |
||||
|
||||
/** |
||||
* base32编码(Hex模式) |
||||
* |
||||
* @param source 被编码的base32字符串 |
||||
* @return 被加密后的字符串 |
||||
*/ |
||||
public static String encodeHex(String source) { |
||||
return encodeHex(source, CharsetUtil.CHARSET_UTF_8); |
||||
} |
||||
|
||||
/** |
||||
* base32编码(Hex模式) |
||||
* |
||||
* @param source 被编码的base32字符串 |
||||
* @param charset 字符集 |
||||
* @return 被加密后的字符串 |
||||
*/ |
||||
public static String encodeHex(String source, Charset charset) { |
||||
return encodeHex(StrUtil.bytes(source, charset)); |
||||
} |
||||
|
||||
//----------------------------------------------------------------------------------------- decode
|
||||
|
||||
/** |
||||
* 解码 |
||||
* |
||||
* @param base32 base32编码 |
||||
* @return 数据 |
||||
*/ |
||||
public static byte[] decode(String base32) { |
||||
return Base32Codec.INSTANCE.decode(base32); |
||||
} |
||||
|
||||
/** |
||||
* base32解码 |
||||
* |
||||
* @param source 被解码的base32字符串 |
||||
* @return 被加密后的字符串 |
||||
*/ |
||||
public static String decodeStr(String source) { |
||||
return decodeStr(source, CharsetUtil.CHARSET_UTF_8); |
||||
} |
||||
|
||||
/** |
||||
* base32解码 |
||||
* |
||||
* @param source 被解码的base32字符串 |
||||
* @param charset 字符集 |
||||
* @return 被加密后的字符串 |
||||
*/ |
||||
public static String decodeStr(String source, Charset charset) { |
||||
return StrUtil.str(decode(source), charset); |
||||
} |
||||
|
||||
/** |
||||
* 解码 |
||||
* |
||||
* @param base32 base32编码 |
||||
* @return 数据 |
||||
*/ |
||||
public static byte[] decodeHex(String base32) { |
||||
return Base32Codec.INSTANCE.decode(base32, true); |
||||
} |
||||
|
||||
/** |
||||
* base32解码 |
||||
* |
||||
* @param source 被解码的base32字符串 |
||||
* @return 被加密后的字符串 |
||||
*/ |
||||
public static String decodeStrHex(String source) { |
||||
return decodeStrHex(source, CharsetUtil.CHARSET_UTF_8); |
||||
} |
||||
|
||||
/** |
||||
* base32解码 |
||||
* |
||||
* @param source 被解码的base32字符串 |
||||
* @param charset 字符集 |
||||
* @return 被加密后的字符串 |
||||
*/ |
||||
public static String decodeStrHex(String source, Charset charset) { |
||||
return StrUtil.str(decodeHex(source), charset); |
||||
} |
||||
} |
@ -0,0 +1,215 @@ |
||||
package cn.hutool.core.codec; |
||||
|
||||
import java.util.Arrays; |
||||
|
||||
/** |
||||
* Base32 - encodes and decodes RFC4648 Base32 (see https://datatracker.ietf.org/doc/html/rfc4648#section-6 )<br>
|
||||
* base32就是用32(2的5次方)个特定ASCII码来表示256个ASCII码。<br> |
||||
* 所以,5个ASCII字符经过base32编码后会变为8个字符(公约数为40),长度增加3/5.不足8n用“=”补足。<br> |
||||
* 根据RFC4648 Base32规范,支持两种模式: |
||||
* <ul> |
||||
* <li>Base 32 Alphabet (ABCDEFGHIJKLMNOPQRSTUVWXYZ234567)</li> |
||||
* <li>"Extended Hex" Base 32 Alphabet (0123456789ABCDEFGHIJKLMNOPQRSTUV)</li> |
||||
* </ul> |
||||
* |
||||
* @author Looly |
||||
* @since 5.8.0 |
||||
*/ |
||||
public class Base32Codec implements Encoder<byte[], String>, Decoder<CharSequence, byte[]> { |
||||
|
||||
public static Base32Codec INSTANCE = new Base32Codec(); |
||||
|
||||
@Override |
||||
public String encode(byte[] data) { |
||||
return encode(data, false); |
||||
} |
||||
|
||||
/** |
||||
* 编码数据 |
||||
* |
||||
* @param data 数据 |
||||
* @param useHex 是否使用Hex Alphabet |
||||
* @return 编码后的Base32字符串 |
||||
*/ |
||||
public String encode(byte[] data, boolean useHex) { |
||||
final Base32Encoder encoder = useHex ? Base32Encoder.HEX_ENCODER : Base32Encoder.ENCODER; |
||||
return encoder.encode(data); |
||||
} |
||||
|
||||
@Override |
||||
public byte[] decode(CharSequence encoded) { |
||||
return decode(encoded, false); |
||||
} |
||||
|
||||
/** |
||||
* 解码数据 |
||||
* |
||||
* @param encoded base32字符串 |
||||
* @param useHex 是否使用Hex Alphabet |
||||
* @return 解码后的内容 |
||||
*/ |
||||
public byte[] decode(CharSequence encoded, boolean useHex) { |
||||
final Base32Decoder decoder = useHex ? Base32Decoder.HEX_DECODER : Base32Decoder.DECODER; |
||||
return decoder.decode(encoded); |
||||
} |
||||
|
||||
/** |
||||
* Bas32编码器 |
||||
*/ |
||||
public static class Base32Encoder implements Encoder<byte[], String> { |
||||
private static final String DEFAULT_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; |
||||
private static final String HEX_ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUV"; |
||||
private static final Character DEFAULT_PAD = '='; |
||||
private static final int[] BASE32_FILL = {-1, 4, 1, 6, 3}; |
||||
|
||||
public static final Base32Encoder ENCODER = new Base32Encoder(DEFAULT_ALPHABET, DEFAULT_PAD); |
||||
public static final Base32Encoder HEX_ENCODER = new Base32Encoder(HEX_ALPHABET, DEFAULT_PAD); |
||||
|
||||
private final char[] alphabet; |
||||
private final Character pad; |
||||
|
||||
/** |
||||
* 构造 |
||||
* |
||||
* @param alphabet 自定义编码字母表,见 {@link #DEFAULT_ALPHABET}和 {@link #HEX_ALPHABET} |
||||
* @param pad 补位字符 |
||||
*/ |
||||
public Base32Encoder(String alphabet, Character pad) { |
||||
this.alphabet = alphabet.toCharArray(); |
||||
this.pad = pad; |
||||
} |
||||
|
||||
@Override |
||||
public String encode(byte[] data) { |
||||
int i = 0; |
||||
int index = 0; |
||||
int digit; |
||||
int currByte; |
||||
int nextByte; |
||||
|
||||
int encodeLen = data.length * 8 / 5; |
||||
if (encodeLen != 0) { |
||||
encodeLen = encodeLen + 1 + BASE32_FILL[(data.length * 8) % 5]; |
||||
} |
||||
|
||||
StringBuilder base32 = new StringBuilder(encodeLen); |
||||
|
||||
while (i < data.length) { |
||||
// unsign
|
||||
currByte = (data[i] >= 0) ? data[i] : (data[i] + 256); |
||||
|
||||
/* Is the current digit going to span a byte boundary? */ |
||||
if (index > 3) { |
||||
if ((i + 1) < data.length) { |
||||
nextByte = (data[i + 1] >= 0) ? data[i + 1] : (data[i + 1] + 256); |
||||
} else { |
||||
nextByte = 0; |
||||
} |
||||
|
||||
digit = currByte & (0xFF >> index); |
||||
index = (index + 5) % 8; |
||||
digit <<= index; |
||||
digit |= nextByte >> (8 - index); |
||||
i++; |
||||
} else { |
||||
digit = (currByte >> (8 - (index + 5))) & 0x1F; |
||||
index = (index + 5) % 8; |
||||
if (index == 0) { |
||||
i++; |
||||
} |
||||
} |
||||
base32.append(alphabet[digit]); |
||||
} |
||||
|
||||
if (null != pad) { |
||||
// 末尾补充不足长度的
|
||||
while (base32.length() < encodeLen) { |
||||
base32.append(pad.charValue()); |
||||
} |
||||
} |
||||
|
||||
return base32.toString(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Base32解码器 |
||||
*/ |
||||
public static class Base32Decoder implements Decoder<CharSequence, byte[]> { |
||||
private static final char BASE_CHAR = '0'; |
||||
|
||||
public static final Base32Decoder DECODER = new Base32Decoder(Base32Encoder.DEFAULT_ALPHABET); |
||||
public static final Base32Decoder HEX_DECODER = new Base32Decoder(Base32Encoder.HEX_ALPHABET); |
||||
|
||||
private final byte[] lookupTable; |
||||
|
||||
/** |
||||
* 构造 |
||||
* |
||||
* @param alphabet 编码字母表 |
||||
*/ |
||||
public Base32Decoder(String alphabet) { |
||||
lookupTable = new byte[128]; |
||||
Arrays.fill(lookupTable, (byte) -1); |
||||
|
||||
final int length = alphabet.length(); |
||||
|
||||
char c; |
||||
for (int i = 0; i < length; i++) { |
||||
c = alphabet.charAt(i); |
||||
lookupTable[c - BASE_CHAR] = (byte) i; |
||||
// 支持小写字母解码
|
||||
if(c >= 'A' && c <= 'Z'){ |
||||
lookupTable[Character.toLowerCase(c) - BASE_CHAR] = (byte) i; |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public byte[] decode(CharSequence encoded) { |
||||
int i, index, lookup, offset, digit; |
||||
final String base32 = encoded.toString(); |
||||
int len = base32.endsWith("=") ? base32.indexOf("=") * 5 / 8 : base32.length() * 5 / 8; |
||||
byte[] bytes = new byte[len]; |
||||
|
||||
for (i = 0, index = 0, offset = 0; i < base32.length(); i++) { |
||||
lookup = base32.charAt(i) - BASE_CHAR; |
||||
|
||||
/* Skip chars outside the lookup table */ |
||||
if (lookup < 0 || lookup >= lookupTable.length) { |
||||
continue; |
||||
} |
||||
|
||||
digit = lookupTable[lookup]; |
||||
|
||||
/* If this digit is not in the table, ignore it */ |
||||
if (digit < 0) { |
||||
continue; |
||||
} |
||||
|
||||
if (index <= 3) { |
||||
index = (index + 5) % 8; |
||||
if (index == 0) { |
||||
bytes[offset] |= digit; |
||||
offset++; |
||||
if (offset >= bytes.length) { |
||||
break; |
||||
} |
||||
} else { |
||||
bytes[offset] |= digit << (8 - index); |
||||
} |
||||
} else { |
||||
index = (index + 5) % 8; |
||||
bytes[offset] |= (digit >>> index); |
||||
offset++; |
||||
|
||||
if (offset >= bytes.length) { |
||||
break; |
||||
} |
||||
bytes[offset] |= digit << (8 - index); |
||||
} |
||||
} |
||||
return bytes; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,152 @@ |
||||
package cn.hutool.core.codec; |
||||
|
||||
import cn.hutool.core.exceptions.UtilException; |
||||
import cn.hutool.core.exceptions.ValidateException; |
||||
|
||||
import java.security.MessageDigest; |
||||
import java.security.NoSuchAlgorithmException; |
||||
import java.util.Arrays; |
||||
|
||||
|
||||
/** |
||||
* Base58工具类,提供Base58的编码和解码方案<br> |
||||
* 参考: https://github.com/Anujraval24/Base58Encoding<br>
|
||||
* 规范见:https://en.bitcoin.it/wiki/Base58Check_encoding
|
||||
* |
||||
* @author lin, looly |
||||
* @since 5.7.22 |
||||
*/ |
||||
public class Base58 { |
||||
|
||||
private static final int CHECKSUM_SIZE = 4; |
||||
|
||||
// -------------------------------------------------------------------- encode
|
||||
|
||||
/** |
||||
* Base58编码<br> |
||||
* 包含版本位和校验位 |
||||
* |
||||
* @param version 编码版本,{@code null}表示不包含版本位 |
||||
* @param data 被编码的数组,添加校验和。 |
||||
* @return 编码后的字符串 |
||||
*/ |
||||
public static String encodeChecked(Integer version, byte[] data) { |
||||
return encode(addChecksum(version, data)); |
||||
} |
||||
|
||||
/** |
||||
* Base58编码 |
||||
* |
||||
* @param data 被编码的数据,不带校验和。 |
||||
* @return 编码后的字符串 |
||||
*/ |
||||
public static String encode(byte[] data) { |
||||
return Base58Codec.INSTANCE.encode(data); |
||||
} |
||||
// -------------------------------------------------------------------- decode
|
||||
|
||||
/** |
||||
* Base58解码<br> |
||||
* 解码包含标志位验证和版本呢位去除 |
||||
* |
||||
* @param encoded 被解码的base58字符串 |
||||
* @return 解码后的bytes |
||||
* @throws ValidateException 标志位验证错误抛出此异常 |
||||
*/ |
||||
public static byte[] decodeChecked(CharSequence encoded) throws ValidateException { |
||||
try { |
||||
return decodeChecked(encoded, true); |
||||
} catch (ValidateException ignore) { |
||||
return decodeChecked(encoded, false); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Base58解码<br> |
||||
* 解码包含标志位验证和版本呢位去除 |
||||
* |
||||
* @param encoded 被解码的base58字符串 |
||||
* @param withVersion 是否包含版本位 |
||||
* @return 解码后的bytes |
||||
* @throws ValidateException 标志位验证错误抛出此异常 |
||||
*/ |
||||
public static byte[] decodeChecked(CharSequence encoded, boolean withVersion) throws ValidateException { |
||||
byte[] valueWithChecksum = decode(encoded); |
||||
return verifyAndRemoveChecksum(valueWithChecksum, withVersion); |
||||
} |
||||
|
||||
/** |
||||
* Base58解码 |
||||
* |
||||
* @param encoded 被编码的base58字符串 |
||||
* @return 解码后的bytes |
||||
*/ |
||||
public static byte[] decode(CharSequence encoded) { |
||||
return Base58Codec.INSTANCE.decode(encoded); |
||||
} |
||||
|
||||
/** |
||||
* 验证并去除验证位和版本位 |
||||
* |
||||
* @param data 编码的数据 |
||||
* @param withVersion 是否包含版本位 |
||||
* @return 载荷数据 |
||||
*/ |
||||
private static byte[] verifyAndRemoveChecksum(byte[] data, boolean withVersion) { |
||||
final byte[] payload = Arrays.copyOfRange(data, withVersion ? 1 : 0, data.length - CHECKSUM_SIZE); |
||||
final byte[] checksum = Arrays.copyOfRange(data, data.length - CHECKSUM_SIZE, data.length); |
||||
final byte[] expectedChecksum = checksum(payload); |
||||
if (!Arrays.equals(checksum, expectedChecksum)) { |
||||
throw new ValidateException("Base58 checksum is invalid"); |
||||
} |
||||
return payload; |
||||
} |
||||
|
||||
/** |
||||
* 数据 + 校验码 |
||||
* |
||||
* @param version 版本,{@code null}表示不添加版本位 |
||||
* @param payload Base58数据(不含校验码) |
||||
* @return Base58数据 |
||||
*/ |
||||
private static byte[] addChecksum(Integer version, byte[] payload) { |
||||
final byte[] addressBytes; |
||||
if (null != version) { |
||||
addressBytes = new byte[1 + payload.length + CHECKSUM_SIZE]; |
||||
addressBytes[0] = (byte) version.intValue(); |
||||
System.arraycopy(payload, 0, addressBytes, 1, payload.length); |
||||
} else { |
||||
addressBytes = new byte[payload.length + CHECKSUM_SIZE]; |
||||
System.arraycopy(payload, 0, addressBytes, 0, payload.length); |
||||
} |
||||
final byte[] checksum = checksum(payload); |
||||
System.arraycopy(checksum, 0, addressBytes, addressBytes.length - CHECKSUM_SIZE, CHECKSUM_SIZE); |
||||
return addressBytes; |
||||
} |
||||
|
||||
/** |
||||
* 获取校验码<br> |
||||
* 计算规则为对数据进行两次sha256计算,然后取{@link #CHECKSUM_SIZE}长度 |
||||
* |
||||
* @param data 数据 |
||||
* @return 校验码 |
||||
*/ |
||||
private static byte[] checksum(byte[] data) { |
||||
byte[] hash = hash256(hash256(data)); |
||||
return Arrays.copyOfRange(hash, 0, CHECKSUM_SIZE); |
||||
} |
||||
|
||||
/** |
||||
* 计算数据的SHA-256值 |
||||
* |
||||
* @param data 数据 |
||||
* @return sha-256值 |
||||
*/ |
||||
private static byte[] hash256(byte[] data) { |
||||
try { |
||||
return MessageDigest.getInstance("SHA-256").digest(data); |
||||
} catch (NoSuchAlgorithmException e) { |
||||
throw new UtilException(e); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,187 @@ |
||||
package cn.hutool.core.codec; |
||||
|
||||
import cn.hutool.core.util.StrUtil; |
||||
|
||||
import java.util.Arrays; |
||||
|
||||
/** |
||||
* Base58编码器<br> |
||||
* 此编码器不包括校验码、版本等信息 |
||||
* |
||||
* @author lin, looly |
||||
* @since 5.7.22 |
||||
*/ |
||||
public class Base58Codec implements Encoder<byte[], String>, Decoder<CharSequence, byte[]> { |
||||
|
||||
public static Base58Codec INSTANCE = new Base58Codec(); |
||||
|
||||
/** |
||||
* Base58编码 |
||||
* |
||||
* @param data 被编码的数据,不带校验和。 |
||||
* @return 编码后的字符串 |
||||
*/ |
||||
@Override |
||||
public String encode(byte[] data) { |
||||
return Base58Encoder.ENCODER.encode(data); |
||||
} |
||||
|
||||
/** |
||||
* 解码给定的Base58字符串 |
||||
* |
||||
* @param encoded Base58编码字符串 |
||||
* @return 解码后的bytes |
||||
* @throws IllegalArgumentException 非标准Base58字符串 |
||||
*/ |
||||
@Override |
||||
public byte[] decode(CharSequence encoded) throws IllegalArgumentException { |
||||
return Base58Decoder.DECODER.decode(encoded); |
||||
} |
||||
|
||||
/** |
||||
* Base58编码器 |
||||
* |
||||
* @since 5.8.0 |
||||
*/ |
||||
public static class Base58Encoder implements Encoder<byte[], String> { |
||||
private static final String DEFAULT_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; |
||||
|
||||
public static final Base58Encoder ENCODER = new Base58Encoder(DEFAULT_ALPHABET.toCharArray()); |
||||
|
||||
private final char[] alphabet; |
||||
private final char alphabetZero; |
||||
|
||||
/** |
||||
* 构造 |
||||
* |
||||
* @param alphabet 编码字母表 |
||||
*/ |
||||
public Base58Encoder(char[] alphabet) { |
||||
this.alphabet = alphabet; |
||||
alphabetZero = alphabet[0]; |
||||
} |
||||
|
||||
@Override |
||||
public String encode(byte[] data) { |
||||
if (null == data) { |
||||
return null; |
||||
} |
||||
if (data.length == 0) { |
||||
return StrUtil.EMPTY; |
||||
} |
||||
// 计算开头0的个数
|
||||
int zeroCount = 0; |
||||
while (zeroCount < data.length && data[zeroCount] == 0) { |
||||
++zeroCount; |
||||
} |
||||
// 将256位编码转换为58位编码
|
||||
data = Arrays.copyOf(data, data.length); // since we modify it in-place
|
||||
final char[] encoded = new char[data.length * 2]; // upper bound
|
||||
int outputStart = encoded.length; |
||||
for (int inputStart = zeroCount; inputStart < data.length; ) { |
||||
encoded[--outputStart] = alphabet[divmod(data, inputStart, 256, 58)]; |
||||
if (data[inputStart] == 0) { |
||||
++inputStart; // optimization - skip leading zeros
|
||||
} |
||||
} |
||||
// Preserve exactly as many leading encoded zeros in output as there were leading zeros in input.
|
||||
while (outputStart < encoded.length && encoded[outputStart] == alphabetZero) { |
||||
++outputStart; |
||||
} |
||||
while (--zeroCount >= 0) { |
||||
encoded[--outputStart] = alphabetZero; |
||||
} |
||||
// Return encoded string (including encoded leading zeros).
|
||||
return new String(encoded, outputStart, encoded.length - outputStart); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Base58解码器 |
||||
* |
||||
* @since 5.8.0 |
||||
*/ |
||||
public static class Base58Decoder implements Decoder<CharSequence, byte[]> { |
||||
|
||||
public static Base58Decoder DECODER = new Base58Decoder(Base58Encoder.DEFAULT_ALPHABET); |
||||
|
||||
private final byte[] lookupTable; |
||||
|
||||
/** |
||||
* 构造 |
||||
* |
||||
* @param alphabet 编码字符表 |
||||
*/ |
||||
public Base58Decoder(String alphabet) { |
||||
final byte[] lookupTable = new byte['z' + 1]; |
||||
Arrays.fill(lookupTable, (byte) -1); |
||||
|
||||
final int length = alphabet.length(); |
||||
for (int i = 0; i < length; i++) { |
||||
lookupTable[alphabet.charAt(i)] = (byte) i; |
||||
} |
||||
this.lookupTable = lookupTable; |
||||
} |
||||
|
||||
@Override |
||||
public byte[] decode(CharSequence encoded) { |
||||
if (encoded.length() == 0) { |
||||
return new byte[0]; |
||||
} |
||||
// Convert the base58-encoded ASCII chars to a base58 byte sequence (base58 digits).
|
||||
final byte[] input58 = new byte[encoded.length()]; |
||||
for (int i = 0; i < encoded.length(); ++i) { |
||||
char c = encoded.charAt(i); |
||||
int digit = c < 128 ? lookupTable[c] : -1; |
||||
if (digit < 0) { |
||||
throw new IllegalArgumentException(StrUtil.format("Invalid char '{}' at [{}]", c, i)); |
||||
} |
||||
input58[i] = (byte) digit; |
||||
} |
||||
// Count leading zeros.
|
||||
int zeros = 0; |
||||
while (zeros < input58.length && input58[zeros] == 0) { |
||||
++zeros; |
||||
} |
||||
// Convert base-58 digits to base-256 digits.
|
||||
byte[] decoded = new byte[encoded.length()]; |
||||
int outputStart = decoded.length; |
||||
for (int inputStart = zeros; inputStart < input58.length; ) { |
||||
decoded[--outputStart] = divmod(input58, inputStart, 58, 256); |
||||
if (input58[inputStart] == 0) { |
||||
++inputStart; // optimization - skip leading zeros
|
||||
} |
||||
} |
||||
// Ignore extra leading zeroes that were added during the calculation.
|
||||
while (outputStart < decoded.length && decoded[outputStart] == 0) { |
||||
++outputStart; |
||||
} |
||||
// Return decoded data (including original number of leading zeros).
|
||||
return Arrays.copyOfRange(decoded, outputStart - zeros, decoded.length); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Divides a number, represented as an array of bytes each containing a single digit |
||||
* in the specified base, by the given divisor. The given number is modified in-place |
||||
* to contain the quotient, and the return value is the remainder. |
||||
* |
||||
* @param number the number to divide |
||||
* @param firstDigit the index within the array of the first non-zero digit |
||||
* (this is used for optimization by skipping the leading zeros) |
||||
* @param base the base in which the number's digits are represented (up to 256) |
||||
* @param divisor the number to divide by (up to 256) |
||||
* @return the remainder of the division operation |
||||
*/ |
||||
private static byte divmod(byte[] number, int firstDigit, int base, int divisor) { |
||||
// this is just long division which accounts for the base of the input digits
|
||||
int remainder = 0; |
||||
for (int i = firstDigit; i < number.length; i++) { |
||||
int digit = (int) number[i] & 0xFF; |
||||
int temp = remainder * base + digit; |
||||
number[i] = (byte) (temp / divisor); |
||||
remainder = temp % divisor; |
||||
} |
||||
return (byte) remainder; |
||||
} |
||||
} |
@ -0,0 +1,262 @@ |
||||
package cn.hutool.core.codec; |
||||
|
||||
import cn.hutool.core.io.FileUtil; |
||||
import cn.hutool.core.io.IoUtil; |
||||
import cn.hutool.core.util.CharsetUtil; |
||||
import cn.hutool.core.util.StrUtil; |
||||
|
||||
import java.io.File; |
||||
import java.io.InputStream; |
||||
import java.io.OutputStream; |
||||
import java.nio.charset.Charset; |
||||
|
||||
/** |
||||
* Base62工具类,提供Base62的编码和解码方案<br> |
||||
* |
||||
* @author Looly |
||||
* @since 4.5.9 |
||||
*/ |
||||
public class Base62 { |
||||
|
||||
private static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; |
||||
|
||||
// -------------------------------------------------------------------- encode
|
||||
/** |
||||
* Base62编码 |
||||
* |
||||
* @param source 被编码的Base62字符串 |
||||
* @return 被加密后的字符串 |
||||
*/ |
||||
public static String encode(CharSequence source) { |
||||
return encode(source, DEFAULT_CHARSET); |
||||
} |
||||
|
||||
/** |
||||
* Base62编码 |
||||
* |
||||
* @param source 被编码的Base62字符串 |
||||
* @param charset 字符集 |
||||
* @return 被加密后的字符串 |
||||
*/ |
||||
public static String encode(CharSequence source, Charset charset) { |
||||
return encode(StrUtil.bytes(source, charset)); |
||||
} |
||||
|
||||
/** |
||||
* Base62编码 |
||||
* |
||||
* @param source 被编码的Base62字符串 |
||||
* @return 被加密后的字符串 |
||||
*/ |
||||
public static String encode(byte[] source) { |
||||
return new String(Base62Codec.INSTANCE.encode(source)); |
||||
} |
||||
|
||||
/** |
||||
* Base62编码 |
||||
* |
||||
* @param in 被编码Base62的流(一般为图片流或者文件流) |
||||
* @return 被加密后的字符串 |
||||
*/ |
||||
public static String encode(InputStream in) { |
||||
return encode(IoUtil.readBytes(in)); |
||||
} |
||||
|
||||
/** |
||||
* Base62编码 |
||||
* |
||||
* @param file 被编码Base62的文件 |
||||
* @return 被加密后的字符串 |
||||
*/ |
||||
public static String encode(File file) { |
||||
return encode(FileUtil.readBytes(file)); |
||||
} |
||||
|
||||
/** |
||||
* Base62编码(反转字母表模式) |
||||
* |
||||
* @param source 被编码的Base62字符串 |
||||
* @return 被加密后的字符串 |
||||
*/ |
||||
public static String encodeInverted(CharSequence source) { |
||||
return encodeInverted(source, DEFAULT_CHARSET); |
||||
} |
||||
|
||||
/** |
||||
* Base62编码(反转字母表模式) |
||||
* |
||||
* @param source 被编码的Base62字符串 |
||||
* @param charset 字符集 |
||||
* @return 被加密后的字符串 |
||||
*/ |
||||
public static String encodeInverted(CharSequence source, Charset charset) { |
||||
return encodeInverted(StrUtil.bytes(source, charset)); |
||||
} |
||||
|
||||
/** |
||||
* Base62编码(反转字母表模式) |
||||
* |
||||
* @param source 被编码的Base62字符串 |
||||
* @return 被加密后的字符串 |
||||
*/ |
||||
public static String encodeInverted(byte[] source) { |
||||
return new String(Base62Codec.INSTANCE.encode(source, true)); |
||||
} |
||||
|
||||
/** |
||||
* Base62编码 |
||||
* |
||||
* @param in 被编码Base62的流(一般为图片流或者文件流) |
||||
* @return 被加密后的字符串 |
||||
*/ |
||||
public static String encodeInverted(InputStream in) { |
||||
return encodeInverted(IoUtil.readBytes(in)); |
||||
} |
||||
|
||||
/** |
||||
* Base62编码(反转字母表模式) |
||||
* |
||||
* @param file 被编码Base62的文件 |
||||
* @return 被加密后的字符串 |
||||
*/ |
||||
public static String encodeInverted(File file) { |
||||
return encodeInverted(FileUtil.readBytes(file)); |
||||
} |
||||
|
||||
// -------------------------------------------------------------------- decode
|
||||
/** |
||||
* Base62解码 |
||||
* |
||||
* @param source 被解码的Base62字符串 |
||||
* @return 被加密后的字符串 |
||||
*/ |
||||
public static String decodeStrGbk(CharSequence source) { |
||||
return decodeStr(source, CharsetUtil.CHARSET_GBK); |
||||
} |
||||
|
||||
/** |
||||
* Base62解码 |
||||
* |
||||
* @param source 被解码的Base62字符串 |
||||
* @return 被加密后的字符串 |
||||
*/ |
||||
public static String decodeStr(CharSequence source) { |
||||
return decodeStr(source, DEFAULT_CHARSET); |
||||
} |
||||
|
||||
/** |
||||
* Base62解码 |
||||
* |
||||
* @param source 被解码的Base62字符串 |
||||
* @param charset 字符集 |
||||
* @return 被加密后的字符串 |
||||
*/ |
||||
public static String decodeStr(CharSequence source, Charset charset) { |
||||
return StrUtil.str(decode(source), charset); |
||||
} |
||||
|
||||
/** |
||||
* Base62解码 |
||||
* |
||||
* @param Base62 被解码的Base62字符串 |
||||
* @param destFile 目标文件 |
||||
* @return 目标文件 |
||||
*/ |
||||
public static File decodeToFile(CharSequence Base62, File destFile) { |
||||
return FileUtil.writeBytes(decode(Base62), destFile); |
||||
} |
||||
|
||||
/** |
||||
* Base62解码 |
||||
* |
||||
* @param base62Str 被解码的Base62字符串 |
||||
* @param out 写出到的流 |
||||
* @param isCloseOut 是否关闭输出流 |
||||
*/ |
||||
public static void decodeToStream(CharSequence base62Str, OutputStream out, boolean isCloseOut) { |
||||
IoUtil.write(out, isCloseOut, decode(base62Str)); |
||||
} |
||||
|
||||
/** |
||||
* Base62解码 |
||||
* |
||||
* @param base62Str 被解码的Base62字符串 |
||||
* @return 被加密后的字符串 |
||||
*/ |
||||
public static byte[] decode(CharSequence base62Str) { |
||||
return decode(StrUtil.bytes(base62Str, DEFAULT_CHARSET)); |
||||
} |
||||
|
||||
/** |
||||
* 解码Base62 |
||||
* |
||||
* @param base62bytes Base62输入 |
||||
* @return 解码后的bytes |
||||
*/ |
||||
public static byte[] decode(byte[] base62bytes) { |
||||
return Base62Codec.INSTANCE.decode(base62bytes); |
||||
} |
||||
|
||||
/** |
||||
* Base62解码(反转字母表模式) |
||||
* |
||||
* @param source 被解码的Base62字符串 |
||||
* @return 被加密后的字符串 |
||||
*/ |
||||
public static String decodeStrInverted(CharSequence source) { |
||||
return decodeStrInverted(source, DEFAULT_CHARSET); |
||||
} |
||||
|
||||
/** |
||||
* Base62解码(反转字母表模式) |
||||
* |
||||
* @param source 被解码的Base62字符串 |
||||
* @param charset 字符集 |
||||
* @return 被加密后的字符串 |
||||
*/ |
||||
public static String decodeStrInverted(CharSequence source, Charset charset) { |
||||
return StrUtil.str(decodeInverted(source), charset); |
||||
} |
||||
|
||||
/** |
||||
* Base62解码(反转字母表模式) |
||||
* |
||||
* @param Base62 被解码的Base62字符串 |
||||
* @param destFile 目标文件 |
||||
* @return 目标文件 |
||||
*/ |
||||
public static File decodeToFileInverted(CharSequence Base62, File destFile) { |
||||
return FileUtil.writeBytes(decodeInverted(Base62), destFile); |
||||
} |
||||
|
||||
/** |
||||
* Base62解码(反转字母表模式) |
||||
* |
||||
* @param base62Str 被解码的Base62字符串 |
||||
* @param out 写出到的流 |
||||
* @param isCloseOut 是否关闭输出流 |
||||
*/ |
||||
public static void decodeToStreamInverted(CharSequence base62Str, OutputStream out, boolean isCloseOut) { |
||||
IoUtil.write(out, isCloseOut, decodeInverted(base62Str)); |
||||
} |
||||
|
||||
/** |
||||
* Base62解码(反转字母表模式) |
||||
* |
||||
* @param base62Str 被解码的Base62字符串 |
||||
* @return 被加密后的字符串 |
||||
*/ |
||||
public static byte[] decodeInverted(CharSequence base62Str) { |
||||
return decodeInverted(StrUtil.bytes(base62Str, DEFAULT_CHARSET)); |
||||
} |
||||
|
||||
/** |
||||
* 解码Base62(反转字母表模式) |
||||
* |
||||
* @param base62bytes Base62输入 |
||||
* @return 解码后的bytes |
||||
*/ |
||||
public static byte[] decodeInverted(byte[] base62bytes) { |
||||
return Base62Codec.INSTANCE.decode(base62bytes, true); |
||||
} |
||||
} |
@ -0,0 +1,232 @@ |
||||
package cn.hutool.core.codec; |
||||
|
||||
import cn.hutool.core.util.ArrayUtil; |
||||
|
||||
import java.io.ByteArrayOutputStream; |
||||
import java.io.Serializable; |
||||
|
||||
/** |
||||
* Base62编码解码实现,常用于短URL<br> |
||||
* From https://github.com/seruco/base62
|
||||
* |
||||
* @author Looly, Sebastian Ruhleder, sebastian@seruco.io |
||||
* @since 4.5.9 |
||||
*/ |
||||
public class Base62Codec implements Encoder<byte[], byte[]>, Decoder<byte[], byte[]>, Serializable { |
||||
private static final long serialVersionUID = 1L; |
||||
|
||||
private static final int STANDARD_BASE = 256; |
||||
private static final int TARGET_BASE = 62; |
||||
|
||||
public static Base62Codec INSTANCE = new Base62Codec(); |
||||
|
||||
/** |
||||
* 编码指定消息bytes为Base62格式的bytes |
||||
* |
||||
* @param data 被编码的消息 |
||||
* @return Base62内容 |
||||
*/ |
||||
@Override |
||||
public byte[] encode(byte[] data) { |
||||
return encode(data, false); |
||||
} |
||||
|
||||
/** |
||||
* 编码指定消息bytes为Base62格式的bytes |
||||
* |
||||
* @param data 被编码的消息 |
||||
* @param useInverted 是否使用反转风格,即将GMP风格中的大小写做转换 |
||||
* @return Base62内容 |
||||
*/ |
||||
public byte[] encode(byte[] data, boolean useInverted) { |
||||
final Base62Encoder encoder = useInverted ? Base62Encoder.INVERTED_ENCODER : Base62Encoder.GMP_ENCODER; |
||||
return encoder.encode(data); |
||||
} |
||||
|
||||
/** |
||||
* 解码Base62消息 |
||||
* |
||||
* @param encoded Base62内容 |
||||
* @return 消息 |
||||
*/ |
||||
@Override |
||||
public byte[] decode(byte[] encoded) { |
||||
return decode(encoded, false); |
||||
} |
||||
|
||||
/** |
||||
* 解码Base62消息 |
||||
* |
||||
* @param encoded Base62内容 |
||||
* @param useInverted 是否使用反转风格,即将GMP风格中的大小写做转换 |
||||
* @return 消息 |
||||
*/ |
||||
public byte[] decode(byte[] encoded, boolean useInverted) { |
||||
final Base62Decoder decoder = useInverted ? Base62Decoder.INVERTED_DECODER : Base62Decoder.GMP_DECODER; |
||||
return decoder.decode(encoded); |
||||
} |
||||
|
||||
/** |
||||
* Base62编码器 |
||||
* |
||||
* @since 5.8.0 |
||||
*/ |
||||
public static class Base62Encoder implements Encoder<byte[], byte[]> { |
||||
/** |
||||
* GMP风格 |
||||
*/ |
||||
private static final byte[] GMP = { //
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', //
|
||||
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F', //
|
||||
'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', //
|
||||
'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', //
|
||||
'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', //
|
||||
'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', //
|
||||
'm', 'n', 'o', 'p', 'q', 'r', 's', 't', //
|
||||
'u', 'v', 'w', 'x', 'y', 'z' //
|
||||
}; |
||||
|
||||
/** |
||||
* 反转风格,即将GMP风格中的大小写做转换 |
||||
*/ |
||||
private static final byte[] INVERTED = { //
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', //
|
||||
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f', //
|
||||
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', //
|
||||
'o', 'p', 'q', 'r', 's', 't', 'u', 'v', //
|
||||
'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', //
|
||||
'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', //
|
||||
'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', //
|
||||
'U', 'V', 'W', 'X', 'Y', 'Z' //
|
||||
}; |
||||
|
||||
public static Base62Encoder GMP_ENCODER = new Base62Encoder(GMP); |
||||
public static Base62Encoder INVERTED_ENCODER = new Base62Encoder(INVERTED); |
||||
|
||||
private final byte[] alphabet; |
||||
|
||||
/** |
||||
* 构造 |
||||
* |
||||
* @param alphabet 字符表 |
||||
*/ |
||||
public Base62Encoder(byte[] alphabet) { |
||||
this.alphabet = alphabet; |
||||
} |
||||
|
||||
@Override |
||||
public byte[] encode(byte[] data) { |
||||
final byte[] indices = convert(data, STANDARD_BASE, TARGET_BASE); |
||||
return translate(indices, alphabet); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Base62解码器 |
||||
* |
||||
* @since 5.8.0 |
||||
*/ |
||||
public static class Base62Decoder implements Decoder<byte[], byte[]> { |
||||
|
||||
public static Base62Decoder GMP_DECODER = new Base62Decoder(Base62Encoder.GMP); |
||||
public static Base62Decoder INVERTED_DECODER = new Base62Decoder(Base62Encoder.INVERTED); |
||||
|
||||
private final byte[] lookupTable; |
||||
|
||||
/** |
||||
* 构造 |
||||
* |
||||
* @param alphabet 字母表 |
||||
*/ |
||||
public Base62Decoder(byte[] alphabet) { |
||||
lookupTable = new byte['z' + 1]; |
||||
for (int i = 0; i < alphabet.length; i++) { |
||||
lookupTable[alphabet[i]] = (byte) i; |
||||
} |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public byte[] decode(byte[] encoded) { |
||||
final byte[] prepared = translate(encoded, lookupTable); |
||||
return convert(prepared, TARGET_BASE, STANDARD_BASE); |
||||
} |
||||
} |
||||
|
||||
// region Private Methods
|
||||
|
||||
/** |
||||
* 按照字典转换bytes |
||||
* |
||||
* @param indices 内容 |
||||
* @param dictionary 字典 |
||||
* @return 转换值 |
||||
*/ |
||||
private static byte[] translate(byte[] indices, byte[] dictionary) { |
||||
final byte[] translation = new byte[indices.length]; |
||||
|
||||
for (int i = 0; i < indices.length; i++) { |
||||
translation[i] = dictionary[indices[i]]; |
||||
} |
||||
|
||||
return translation; |
||||
} |
||||
|
||||
/** |
||||
* 使用定义的字母表从源基准到目标基准 |
||||
* |
||||
* @param message 消息bytes |
||||
* @param sourceBase 源基准长度 |
||||
* @param targetBase 目标基准长度 |
||||
* @return 计算结果 |
||||
*/ |
||||
private static byte[] convert(byte[] message, int sourceBase, int targetBase) { |
||||
// 计算结果长度,算法来自:http://codegolf.stackexchange.com/a/21672
|
||||
final int estimatedLength = estimateOutputLength(message.length, sourceBase, targetBase); |
||||
|
||||
final ByteArrayOutputStream out = new ByteArrayOutputStream(estimatedLength); |
||||
|
||||
byte[] source = message; |
||||
|
||||
while (source.length > 0) { |
||||
final ByteArrayOutputStream quotient = new ByteArrayOutputStream(source.length); |
||||
|
||||
int remainder = 0; |
||||
|
||||
for (byte b : source) { |
||||
final int accumulator = (b & 0xFF) + remainder * sourceBase; |
||||
final int digit = (accumulator - (accumulator % targetBase)) / targetBase; |
||||
|
||||
remainder = accumulator % targetBase; |
||||
|
||||
if (quotient.size() > 0 || digit > 0) { |
||||
quotient.write(digit); |
||||
} |
||||
} |
||||
|
||||
out.write(remainder); |
||||
|
||||
source = quotient.toByteArray(); |
||||
} |
||||
|
||||
// pad output with zeroes corresponding to the number of leading zeroes in the message
|
||||
for (int i = 0; i < message.length - 1 && message[i] == 0; i++) { |
||||
out.write(0); |
||||
} |
||||
|
||||
return ArrayUtil.reverse(out.toByteArray()); |
||||
} |
||||
|
||||
/** |
||||
* 估算结果长度 |
||||
* |
||||
* @param inputLength 输入长度 |
||||
* @param sourceBase 源基准长度 |
||||
* @param targetBase 目标基准长度 |
||||
* @return 估算长度 |
||||
*/ |
||||
private static int estimateOutputLength(int inputLength, int sourceBase, int targetBase) { |
||||
return (int) Math.ceil((Math.log(sourceBase) / Math.log(targetBase)) * inputLength); |
||||
} |
||||
// endregion
|
||||
} |
@ -0,0 +1,386 @@ |
||||
package cn.hutool.core.codec; |
||||
|
||||
import cn.hutool.core.io.FileUtil; |
||||
import cn.hutool.core.io.IoUtil; |
||||
import cn.hutool.core.util.CharsetUtil; |
||||
import cn.hutool.core.util.StrUtil; |
||||
|
||||
import java.io.File; |
||||
import java.io.InputStream; |
||||
import java.io.OutputStream; |
||||
import java.nio.charset.Charset; |
||||
|
||||
/** |
||||
* Base64工具类,提供Base64的编码和解码方案<br> |
||||
* base64编码是用64(2的6次方)个ASCII字符来表示256(2的8次方)个ASCII字符,<br> |
||||
* 也就是三位二进制数组经过编码后变为四位的ASCII字符显示,长度比原来增加1/3。 |
||||
* |
||||
* @author Looly |
||||
*/ |
||||
public class Base64 { |
||||
|
||||
private static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; |
||||
// -------------------------------------------------------------------- encode
|
||||
|
||||
/** |
||||
* 编码为Base64,非URL安全的 |
||||
* |
||||
* @param arr 被编码的数组 |
||||
* @param lineSep 在76个char之后是CRLF还是EOF |
||||
* @return 编码后的bytes |
||||
*/ |
||||
public static byte[] encode(byte[] arr, boolean lineSep) { |
||||
return lineSep ? |
||||
java.util.Base64.getMimeEncoder().encode(arr) : |
||||
java.util.Base64.getEncoder().encode(arr); |
||||
} |
||||
|
||||
/** |
||||
* 编码为Base64,URL安全的 |
||||
* |
||||
* @param arr 被编码的数组 |
||||
* @param lineSep 在76个char之后是CRLF还是EOF |
||||
* @return 编码后的bytes |
||||
* @since 3.0.6 |
||||
* @deprecated 按照RFC2045规范,URL安全的Base64无需换行 |
||||
*/ |
||||
@Deprecated |
||||
public static byte[] encodeUrlSafe(byte[] arr, boolean lineSep) { |
||||
return Base64Encoder.encodeUrlSafe(arr, lineSep); |
||||
} |
||||
|
||||
/** |
||||
* base64编码 |
||||
* |
||||
* @param source 被编码的base64字符串 |
||||
* @return 被加密后的字符串 |
||||
*/ |
||||
public static String encode(CharSequence source) { |
||||
return encode(source, DEFAULT_CHARSET); |
||||
} |
||||
|
||||
/** |
||||
* base64编码,URL安全 |
||||
* |
||||
* @param source 被编码的base64字符串 |
||||
* @return 被加密后的字符串 |
||||
* @since 3.0.6 |
||||
*/ |
||||
public static String encodeUrlSafe(CharSequence source) { |
||||
return encodeUrlSafe(source, DEFAULT_CHARSET); |
||||
} |
||||
|
||||
/** |
||||
* base64编码 |
||||
* |
||||
* @param source 被编码的base64字符串 |
||||
* @param charset 字符集 |
||||
* @return 被加密后的字符串 |
||||
*/ |
||||
public static String encode(CharSequence source, String charset) { |
||||
return encode(source, CharsetUtil.charset(charset)); |
||||
} |
||||
|
||||
/** |
||||
* base64编码,不进行padding(末尾不会填充'=') |
||||
* |
||||
* @param source 被编码的base64字符串 |
||||
* @param charset 编码 |
||||
* @return 被加密后的字符串 |
||||
* @since 5.5.2 |
||||
*/ |
||||
public static String encodeWithoutPadding(CharSequence source, String charset) { |
||||
return encodeWithoutPadding(StrUtil.bytes(source, charset)); |
||||
} |
||||
|
||||
/** |
||||
* base64编码,URL安全 |
||||
* |
||||
* @param source 被编码的base64字符串 |
||||
* @param charset 字符集 |
||||
* @return 被加密后的字符串 |
||||
* @since 3.0.6 |
||||
* @deprecated 请使用 {@link #encodeUrlSafe(CharSequence, Charset)} |
||||
*/ |
||||
@Deprecated |
||||
public static String encodeUrlSafe(CharSequence source, String charset) { |
||||
return encodeUrlSafe(source, CharsetUtil.charset(charset)); |
||||
} |
||||
|
||||
/** |
||||
* base64编码 |
||||
* |
||||
* @param source 被编码的base64字符串 |
||||
* @param charset 字符集 |
||||
* @return 被编码后的字符串 |
||||
*/ |
||||
public static String encode(CharSequence source, Charset charset) { |
||||
return encode(StrUtil.bytes(source, charset)); |
||||
} |
||||
|
||||
/** |
||||
* base64编码,URL安全的 |
||||
* |
||||
* @param source 被编码的base64字符串 |
||||
* @param charset 字符集 |
||||
* @return 被加密后的字符串 |
||||
* @since 3.0.6 |
||||
*/ |
||||
public static String encodeUrlSafe(CharSequence source, Charset charset) { |
||||
return encodeUrlSafe(StrUtil.bytes(source, charset)); |
||||
} |
||||
|
||||
/** |
||||
* base64编码 |
||||
* |
||||
* @param source 被编码的base64字符串 |
||||
* @return 被加密后的字符串 |
||||
*/ |
||||
public static String encode(byte[] source) { |
||||
return java.util.Base64.getEncoder().encodeToString(source); |
||||
} |
||||
|
||||
/** |
||||
* base64编码,不进行padding(末尾不会填充'=') |
||||
* |
||||
* @param source 被编码的base64字符串 |
||||
* @return 被加密后的字符串 |
||||
* @since 5.5.2 |
||||
*/ |
||||
public static String encodeWithoutPadding(byte[] source) { |
||||
return java.util.Base64.getEncoder().withoutPadding().encodeToString(source); |
||||
} |
||||
|
||||
/** |
||||
* base64编码,URL安全的 |
||||
* |
||||
* @param source 被编码的base64字符串 |
||||
* @return 被加密后的字符串 |
||||
* @since 3.0.6 |
||||
*/ |
||||
public static String encodeUrlSafe(byte[] source) { |
||||
return java.util.Base64.getUrlEncoder().withoutPadding().encodeToString(source); |
||||
} |
||||
|
||||
/** |
||||
* base64编码 |
||||
* |
||||
* @param in 被编码base64的流(一般为图片流或者文件流) |
||||
* @return 被加密后的字符串 |
||||
* @since 4.0.9 |
||||
*/ |
||||
public static String encode(InputStream in) { |
||||
return encode(IoUtil.readBytes(in)); |
||||
} |
||||
|
||||
/** |
||||
* base64编码,URL安全的 |
||||
* |
||||
* @param in 被编码base64的流(一般为图片流或者文件流) |
||||
* @return 被加密后的字符串 |
||||
* @since 4.0.9 |
||||
*/ |
||||
public static String encodeUrlSafe(InputStream in) { |
||||
return encodeUrlSafe(IoUtil.readBytes(in)); |
||||
} |
||||
|
||||
/** |
||||
* base64编码 |
||||
* |
||||
* @param file 被编码base64的文件 |
||||
* @return 被加密后的字符串 |
||||
* @since 4.0.9 |
||||
*/ |
||||
public static String encode(File file) { |
||||
return encode(FileUtil.readBytes(file)); |
||||
} |
||||
|
||||
/** |
||||
* base64编码,URL安全的 |
||||
* |
||||
* @param file 被编码base64的文件 |
||||
* @return 被加密后的字符串 |
||||
* @since 4.0.9 |
||||
*/ |
||||
public static String encodeUrlSafe(File file) { |
||||
return encodeUrlSafe(FileUtil.readBytes(file)); |
||||
} |
||||
|
||||
/** |
||||
* 编码为Base64字符串<br> |
||||
* 如果isMultiLine为{@code true},则每76个字符一个换行符,否则在一行显示 |
||||
* |
||||
* @param arr 被编码的数组 |
||||
* @param isMultiLine 在76个char之后是CRLF还是EOF |
||||
* @param isUrlSafe 是否使用URL安全字符,一般为{@code false} |
||||
* @return 编码后的bytes |
||||
* @since 5.7.2 |
||||
*/ |
||||
public static String encodeStr(byte[] arr, boolean isMultiLine, boolean isUrlSafe) { |
||||
return StrUtil.str(encode(arr, isMultiLine, isUrlSafe), DEFAULT_CHARSET); |
||||
} |
||||
|
||||
/** |
||||
* 编码为Base64<br> |
||||
* 如果isMultiLine为{@code true},则每76个字符一个换行符,否则在一行显示 |
||||
* |
||||
* @param arr 被编码的数组 |
||||
* @param isMultiLine 在76个char之后是CRLF还是EOF |
||||
* @param isUrlSafe 是否使用URL安全字符,一般为{@code false} |
||||
* @return 编码后的bytes |
||||
*/ |
||||
public static byte[] encode(byte[] arr, boolean isMultiLine, boolean isUrlSafe) { |
||||
return Base64Encoder.encode(arr, isMultiLine, isUrlSafe); |
||||
} |
||||
|
||||
// -------------------------------------------------------------------- decode
|
||||
|
||||
/** |
||||
* base64解码 |
||||
* |
||||
* @param source 被解码的base64字符串 |
||||
* @return 被加密后的字符串 |
||||
* @since 4.3.2 |
||||
*/ |
||||
public static String decodeStrGbk(CharSequence source) { |
||||
return Base64Decoder.decodeStr(source, CharsetUtil.CHARSET_GBK); |
||||
} |
||||
|
||||
/** |
||||
* base64解码 |
||||
* |
||||
* @param source 被解码的base64字符串 |
||||
* @return 被加密后的字符串 |
||||
*/ |
||||
public static String decodeStr(CharSequence source) { |
||||
return Base64Decoder.decodeStr(source); |
||||
} |
||||
|
||||
/** |
||||
* base64解码 |
||||
* |
||||
* @param source 被解码的base64字符串 |
||||
* @param charset 字符集 |
||||
* @return 被加密后的字符串 |
||||
*/ |
||||
public static String decodeStr(CharSequence source, String charset) { |
||||
return decodeStr(source, CharsetUtil.charset(charset)); |
||||
} |
||||
|
||||
/** |
||||
* base64解码 |
||||
* |
||||
* @param source 被解码的base64字符串 |
||||
* @param charset 字符集 |
||||
* @return 被加密后的字符串 |
||||
*/ |
||||
public static String decodeStr(CharSequence source, Charset charset) { |
||||
return Base64Decoder.decodeStr(source, charset); |
||||
} |
||||
|
||||
/** |
||||
* base64解码 |
||||
* |
||||
* @param base64 被解码的base64字符串 |
||||
* @param destFile 目标文件 |
||||
* @return 目标文件 |
||||
* @since 4.0.9 |
||||
*/ |
||||
public static File decodeToFile(CharSequence base64, File destFile) { |
||||
return FileUtil.writeBytes(Base64Decoder.decode(base64), destFile); |
||||
} |
||||
|
||||
/** |
||||
* base64解码 |
||||
* |
||||
* @param base64 被解码的base64字符串 |
||||
* @param out 写出到的流 |
||||
* @param isCloseOut 是否关闭输出流 |
||||
* @since 4.0.9 |
||||
*/ |
||||
public static void decodeToStream(CharSequence base64, OutputStream out, boolean isCloseOut) { |
||||
IoUtil.write(out, isCloseOut, Base64Decoder.decode(base64)); |
||||
} |
||||
|
||||
/** |
||||
* base64解码 |
||||
* |
||||
* @param base64 被解码的base64字符串 |
||||
* @return 解码后的bytes |
||||
*/ |
||||
public static byte[] decode(CharSequence base64) { |
||||
return Base64Decoder.decode(base64); |
||||
} |
||||
|
||||
/** |
||||
* 解码Base64 |
||||
* |
||||
* @param in 输入 |
||||
* @return 解码后的bytes |
||||
*/ |
||||
public static byte[] decode(byte[] in) { |
||||
return Base64Decoder.decode(in); |
||||
} |
||||
|
||||
/** |
||||
* 检查是否为Base64 |
||||
* |
||||
* @param base64 Base64的bytes |
||||
* @return 是否为Base64 |
||||
* @since 5.7.5 |
||||
*/ |
||||
public static boolean isBase64(CharSequence base64) { |
||||
if (base64 == null || base64.length() < 2) { |
||||
return false; |
||||
} |
||||
|
||||
final byte[] bytes = StrUtil.utf8Bytes(base64); |
||||
|
||||
if (bytes.length != base64.length()) { |
||||
// 如果长度不相等,说明存在双字节字符,肯定不是Base64,直接返回false
|
||||
return false; |
||||
} |
||||
|
||||
return isBase64(bytes); |
||||
} |
||||
|
||||
/** |
||||
* 检查是否为Base64 |
||||
* |
||||
* @param base64Bytes Base64的bytes |
||||
* @return 是否为Base64 |
||||
* @since 5.7.5 |
||||
*/ |
||||
public static boolean isBase64(byte[] base64Bytes) { |
||||
if (base64Bytes == null || base64Bytes.length < 3) { |
||||
return false; |
||||
} |
||||
boolean hasPadding = false; |
||||
for (byte base64Byte : base64Bytes) { |
||||
if (hasPadding) { |
||||
if ('=' != base64Byte) { |
||||
// 前一个字符是'=',则后边的字符都必须是'=',即'='只能都位于结尾
|
||||
return false; |
||||
} |
||||
} else if ('=' == base64Byte) { |
||||
// 发现'=' 标记之
|
||||
hasPadding = true; |
||||
} else if (!(Base64Decoder.isBase64Code(base64Byte) || isWhiteSpace(base64Byte))) { |
||||
return false; |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
private static boolean isWhiteSpace(byte byteToCheck) { |
||||
switch (byteToCheck) { |
||||
case ' ': |
||||
case '\n': |
||||
case '\r': |
||||
case '\t': |
||||
return true; |
||||
default: |
||||
return false; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,162 @@ |
||||
package cn.hutool.core.codec; |
||||
|
||||
import cn.hutool.core.lang.mutable.MutableInt; |
||||
import cn.hutool.core.util.ArrayUtil; |
||||
import cn.hutool.core.util.CharsetUtil; |
||||
import cn.hutool.core.util.StrUtil; |
||||
|
||||
import java.nio.charset.Charset; |
||||
|
||||
/** |
||||
* Base64解码实现 |
||||
* |
||||
* @author looly |
||||
* |
||||
*/ |
||||
public class Base64Decoder { |
||||
|
||||
private static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; |
||||
private static final byte PADDING = -2; |
||||
|
||||
/** Base64解码表,共128位,-1表示非base64字符,-2表示padding */ |
||||
private static final byte[] DECODE_TABLE = { |
||||
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 00-0f
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 10-1f
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, 62, -1, 63, // 20-2f + - /
|
||||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, // 30-3f 0-9,-2的位置是'='
|
||||
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 40-4f A-O
|
||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, // 50-5f P-Z _
|
||||
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // 60-6f a-o
|
||||
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 // 70-7a p-z
|
||||
}; |
||||
|
||||
/** |
||||
* base64解码 |
||||
* |
||||
* @param source 被解码的base64字符串 |
||||
* @return 被加密后的字符串 |
||||
*/ |
||||
public static String decodeStr(CharSequence source) { |
||||
return decodeStr(source, DEFAULT_CHARSET); |
||||
} |
||||
|
||||
/** |
||||
* base64解码 |
||||
* |
||||
* @param source 被解码的base64字符串 |
||||
* @param charset 字符集 |
||||
* @return 被加密后的字符串 |
||||
*/ |
||||
public static String decodeStr(CharSequence source, Charset charset) { |
||||
return StrUtil.str(decode(source), charset); |
||||
} |
||||
|
||||
/** |
||||
* base64解码 |
||||
* |
||||
* @param source 被解码的base64字符串 |
||||
* @return 被加密后的字符串 |
||||
*/ |
||||
public static byte[] decode(CharSequence source) { |
||||
return decode(StrUtil.bytes(source, DEFAULT_CHARSET)); |
||||
} |
||||
|
||||
/** |
||||
* 解码Base64 |
||||
* |
||||
* @param in 输入 |
||||
* @return 解码后的bytes |
||||
*/ |
||||
public static byte[] decode(byte[] in) { |
||||
if (ArrayUtil.isEmpty(in)) { |
||||
return in; |
||||
} |
||||
return decode(in, 0, in.length); |
||||
} |
||||
|
||||
/** |
||||
* 解码Base64 |
||||
* |
||||
* @param in 输入 |
||||
* @param pos 开始位置 |
||||
* @param length 长度 |
||||
* @return 解码后的bytes |
||||
*/ |
||||
public static byte[] decode(byte[] in, int pos, int length) { |
||||
if (ArrayUtil.isEmpty(in)) { |
||||
return in; |
||||
} |
||||
|
||||
final MutableInt offset = new MutableInt(pos); |
||||
|
||||
byte sestet0; |
||||
byte sestet1; |
||||
byte sestet2; |
||||
byte sestet3; |
||||
int maxPos = pos + length - 1; |
||||
int octetId = 0; |
||||
byte[] octet = new byte[length * 3 / 4];// over-estimated if non-base64 characters present
|
||||
while (offset.intValue() <= maxPos) { |
||||
sestet0 = getNextValidDecodeByte(in, offset, maxPos); |
||||
sestet1 = getNextValidDecodeByte(in, offset, maxPos); |
||||
sestet2 = getNextValidDecodeByte(in, offset, maxPos); |
||||
sestet3 = getNextValidDecodeByte(in, offset, maxPos); |
||||
|
||||
if (PADDING != sestet1) { |
||||
octet[octetId++] = (byte) ((sestet0 << 2) | (sestet1 >>> 4)); |
||||
} |
||||
if (PADDING != sestet2) { |
||||
octet[octetId++] = (byte) (((sestet1 & 0xf) << 4) | (sestet2 >>> 2)); |
||||
} |
||||
if (PADDING != sestet3) { |
||||
octet[octetId++] = (byte) (((sestet2 & 3) << 6) | sestet3); |
||||
} |
||||
} |
||||
|
||||
if (octetId == octet.length) { |
||||
return octet; |
||||
} else { |
||||
// 如果有非Base64字符混入,则实际结果比解析的要短,截取之
|
||||
return (byte[]) ArrayUtil.copy(octet, new byte[octetId], octetId); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 给定的字符是否为Base64字符 |
||||
* |
||||
* @param octet 被检查的字符 |
||||
* @return 是否为Base64字符 |
||||
* @since 5.7.5 |
||||
*/ |
||||
public static boolean isBase64Code(byte octet) { |
||||
return octet == '=' || (octet >= 0 && octet < DECODE_TABLE.length && DECODE_TABLE[octet] != -1); |
||||
} |
||||
|
||||
// ----------------------------------------------------------------------------------------------- Private start
|
||||
/** |
||||
* 获取下一个有效的byte字符 |
||||
* |
||||
* @param in 输入 |
||||
* @param pos 当前位置,调用此方法后此位置保持在有效字符的下一个位置 |
||||
* @param maxPos 最大位置 |
||||
* @return 有效字符,如果达到末尾返回 |
||||
*/ |
||||
private static byte getNextValidDecodeByte(byte[] in, MutableInt pos, int maxPos) { |
||||
byte base64Byte; |
||||
byte decodeByte; |
||||
while (pos.intValue() <= maxPos) { |
||||
base64Byte = in[pos.intValue()]; |
||||
pos.increment(); |
||||
if (base64Byte > -1) { |
||||
decodeByte = DECODE_TABLE[base64Byte]; |
||||
if (decodeByte > -1) { |
||||
return decodeByte; |
||||
} |
||||
} |
||||
} |
||||
// padding if reached max position
|
||||
return PADDING; |
||||
} |
||||
// ----------------------------------------------------------------------------------------------- Private end
|
||||
} |
@ -0,0 +1,214 @@ |
||||
package cn.hutool.core.codec; |
||||
|
||||
import cn.hutool.core.util.CharsetUtil; |
||||
import cn.hutool.core.util.StrUtil; |
||||
|
||||
import java.nio.charset.Charset; |
||||
|
||||
/** |
||||
* Base64编码<br> |
||||
* TODO 6.x移除此类,使用JDK自身 |
||||
* |
||||
* @author looly |
||||
* @since 3.2.0 |
||||
*/ |
||||
public class Base64Encoder { |
||||
|
||||
private static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; |
||||
/** |
||||
* 标准编码表 |
||||
*/ |
||||
private static final byte[] STANDARD_ENCODE_TABLE = { //
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', //
|
||||
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', //
|
||||
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', //
|
||||
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', //
|
||||
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', //
|
||||
'o', 'p', 'q', 'r', 's', 't', 'u', 'v', //
|
||||
'w', 'x', 'y', 'z', '0', '1', '2', '3', //
|
||||
'4', '5', '6', '7', '8', '9', '+', '/' //
|
||||
}; |
||||
/** |
||||
* URL安全的编码表,将 + 和 / 替换为 - 和 _ |
||||
*/ |
||||
private static final byte[] URL_SAFE_ENCODE_TABLE = { //
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', //
|
||||
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', //
|
||||
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', //
|
||||
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', //
|
||||
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', //
|
||||
'o', 'p', 'q', 'r', 's', 't', 'u', 'v', //
|
||||
'w', 'x', 'y', 'z', '0', '1', '2', '3', //
|
||||
'4', '5', '6', '7', '8', '9', '-', '_' //
|
||||
}; |
||||
|
||||
// -------------------------------------------------------------------- encode
|
||||
|
||||
/** |
||||
* 编码为Base64,非URL安全的 |
||||
* |
||||
* @param arr 被编码的数组 |
||||
* @param lineSep 在76个char之后是CRLF还是EOF |
||||
* @return 编码后的bytes |
||||
*/ |
||||
public static byte[] encode(byte[] arr, boolean lineSep) { |
||||
return encode(arr, lineSep, false); |
||||
} |
||||
|
||||
/** |
||||
* 编码为Base64,URL安全的 |
||||
* |
||||
* @param arr 被编码的数组 |
||||
* @param lineSep 在76个char之后是CRLF还是EOF |
||||
* @return 编码后的bytes |
||||
* @since 3.0.6 |
||||
*/ |
||||
public static byte[] encodeUrlSafe(byte[] arr, boolean lineSep) { |
||||
return encode(arr, lineSep, true); |
||||
} |
||||
|
||||
/** |
||||
* base64编码 |
||||
* |
||||
* @param source 被编码的base64字符串 |
||||
* @return 被加密后的字符串 |
||||
*/ |
||||
public static String encode(CharSequence source) { |
||||
return encode(source, DEFAULT_CHARSET); |
||||
} |
||||
|
||||
/** |
||||
* base64编码,URL安全 |
||||
* |
||||
* @param source 被编码的base64字符串 |
||||
* @return 被加密后的字符串 |
||||
* @since 3.0.6 |
||||
*/ |
||||
public static String encodeUrlSafe(CharSequence source) { |
||||
return encodeUrlSafe(source, DEFAULT_CHARSET); |
||||
} |
||||
|
||||
/** |
||||
* base64编码 |
||||
* |
||||
* @param source 被编码的base64字符串 |
||||
* @param charset 字符集 |
||||
* @return 被加密后的字符串 |
||||
*/ |
||||
public static String encode(CharSequence source, Charset charset) { |
||||
return encode(StrUtil.bytes(source, charset)); |
||||
} |
||||
|
||||
/** |
||||
* base64编码,URL安全的 |
||||
* |
||||
* @param source 被编码的base64字符串 |
||||
* @param charset 字符集 |
||||
* @return 被加密后的字符串 |
||||
* @since 3.0.6 |
||||
*/ |
||||
public static String encodeUrlSafe(CharSequence source, Charset charset) { |
||||
return encodeUrlSafe(StrUtil.bytes(source, charset)); |
||||
} |
||||
|
||||
/** |
||||
* base64编码 |
||||
* |
||||
* @param source 被编码的base64字符串 |
||||
* @return 被加密后的字符串 |
||||
*/ |
||||
public static String encode(byte[] source) { |
||||
return StrUtil.str(encode(source, false), DEFAULT_CHARSET); |
||||
} |
||||
|
||||
/** |
||||
* base64编码,URL安全的 |
||||
* |
||||
* @param source 被编码的base64字符串 |
||||
* @return 被加密后的字符串 |
||||
* @since 3.0.6 |
||||
*/ |
||||
public static String encodeUrlSafe(byte[] source) { |
||||
return StrUtil.str(encodeUrlSafe(source, false), DEFAULT_CHARSET); |
||||
} |
||||
|
||||
/** |
||||
* 编码为Base64字符串<br> |
||||
* 如果isMultiLine为{@code true},则每76个字符一个换行符,否则在一行显示 |
||||
* |
||||
* @param arr 被编码的数组 |
||||
* @param isMultiLine 在76个char之后是CRLF还是EOF |
||||
* @param isUrlSafe 是否使用URL安全字符,在URL Safe模式下,=为URL中的关键字符,不需要补充。空余的byte位要去掉,一般为{@code false} |
||||
* @return 编码后的bytes |
||||
* @since 5.7.2 |
||||
*/ |
||||
public static String encodeStr(byte[] arr, boolean isMultiLine, boolean isUrlSafe) { |
||||
return StrUtil.str(encode(arr, isMultiLine, isUrlSafe), DEFAULT_CHARSET); |
||||
} |
||||
|
||||
/** |
||||
* 编码为Base64<br> |
||||
* 如果isMultiLine为{@code true},则每76个字符一个换行符,否则在一行显示 |
||||
* |
||||
* @param arr 被编码的数组 |
||||
* @param isMultiLine 在76个char之后是CRLF还是EOF |
||||
* @param isUrlSafe 是否使用URL安全字符,在URL Safe模式下,=为URL中的关键字符,不需要补充。空余的byte位要去掉,一般为{@code false} |
||||
* @return 编码后的bytes |
||||
*/ |
||||
public static byte[] encode(byte[] arr, boolean isMultiLine, boolean isUrlSafe) { |
||||
if (null == arr) { |
||||
return null; |
||||
} |
||||
|
||||
int len = arr.length; |
||||
if (len == 0) { |
||||
return new byte[0]; |
||||
} |
||||
|
||||
int evenlen = (len / 3) * 3; |
||||
int cnt = ((len - 1) / 3 + 1) << 2; |
||||
int destlen = cnt + (isMultiLine ? (cnt - 1) / 76 << 1 : 0); |
||||
byte[] dest = new byte[destlen]; |
||||
|
||||
byte[] encodeTable = isUrlSafe ? URL_SAFE_ENCODE_TABLE : STANDARD_ENCODE_TABLE; |
||||
|
||||
for (int s = 0, d = 0, cc = 0; s < evenlen; ) { |
||||
int i = (arr[s++] & 0xff) << 16 | (arr[s++] & 0xff) << 8 | (arr[s++] & 0xff); |
||||
|
||||
dest[d++] = encodeTable[(i >>> 18) & 0x3f]; |
||||
dest[d++] = encodeTable[(i >>> 12) & 0x3f]; |
||||
dest[d++] = encodeTable[(i >>> 6) & 0x3f]; |
||||
dest[d++] = encodeTable[i & 0x3f]; |
||||
|
||||
if (isMultiLine && ++cc == 19 && d < destlen - 2) { |
||||
dest[d++] = '\r'; |
||||
dest[d++] = '\n'; |
||||
cc = 0; |
||||
} |
||||
} |
||||
|
||||
int left = len - evenlen;// 剩余位数
|
||||
if (left > 0) { |
||||
int i = ((arr[evenlen] & 0xff) << 10) | (left == 2 ? ((arr[len - 1] & 0xff) << 2) : 0); |
||||
|
||||
dest[destlen - 4] = encodeTable[i >> 12]; |
||||
dest[destlen - 3] = encodeTable[(i >>> 6) & 0x3f]; |
||||
|
||||
if (isUrlSafe) { |
||||
// 在URL Safe模式下,=为URL中的关键字符,不需要补充。空余的byte位要去掉。
|
||||
int urlSafeLen = destlen - 2; |
||||
if (2 == left) { |
||||
dest[destlen - 2] = encodeTable[i & 0x3f]; |
||||
urlSafeLen += 1; |
||||
} |
||||
byte[] urlSafeDest = new byte[urlSafeLen]; |
||||
System.arraycopy(dest, 0, urlSafeDest, 0, urlSafeLen); |
||||
return urlSafeDest; |
||||
} else { |
||||
dest[destlen - 2] = (left == 2) ? encodeTable[i & 0x3f] : (byte) '='; |
||||
dest[destlen - 1] = '='; |
||||
} |
||||
} |
||||
return dest; |
||||
} |
||||
} |
@ -0,0 +1,89 @@ |
||||
package cn.hutool.core.codec; |
||||
|
||||
import cn.hutool.core.lang.Assert; |
||||
|
||||
/** |
||||
* 凯撒密码实现<br> |
||||
* 算法来自:https://github.com/zhaorenjie110/SymmetricEncryptionAndDecryption
|
||||
* |
||||
* @author looly |
||||
*/ |
||||
public class Caesar { |
||||
|
||||
// 26个字母表
|
||||
public static final String TABLE = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz"; |
||||
|
||||
/** |
||||
* 传入明文,加密得到密文 |
||||
* |
||||
* @param message 加密的消息 |
||||
* @param offset 偏移量 |
||||
* @return 加密后的内容 |
||||
*/ |
||||
public static String encode(String message, int offset) { |
||||
Assert.notNull(message, "message must be not null!"); |
||||
final int len = message.length(); |
||||
final char[] plain = message.toCharArray(); |
||||
char c; |
||||
for (int i = 0; i < len; i++) { |
||||
c = message.charAt(i); |
||||
if (!Character.isLetter(c)) { |
||||
continue; |
||||
} |
||||
plain[i] = encodeChar(c, offset); |
||||
} |
||||
return new String(plain); |
||||
} |
||||
|
||||
/** |
||||
* 传入明文解密到密文 |
||||
* |
||||
* @param cipherText 密文 |
||||
* @param offset 偏移量 |
||||
* @return 解密后的内容 |
||||
*/ |
||||
public static String decode(String cipherText, int offset) { |
||||
Assert.notNull(cipherText, "cipherText must be not null!"); |
||||
final int len = cipherText.length(); |
||||
final char[] plain = cipherText.toCharArray(); |
||||
char c; |
||||
for (int i = 0; i < len; i++) { |
||||
c = cipherText.charAt(i); |
||||
if (!Character.isLetter(c)) { |
||||
continue; |
||||
} |
||||
plain[i] = decodeChar(c, offset); |
||||
} |
||||
return new String(plain); |
||||
} |
||||
|
||||
// ----------------------------------------------------------------------------------------- Private method start
|
||||
|
||||
/** |
||||
* 加密轮盘 |
||||
* |
||||
* @param c 被加密字符 |
||||
* @param offset 偏移量 |
||||
* @return 加密后的字符 |
||||
*/ |
||||
private static char encodeChar(char c, int offset) { |
||||
int position = (TABLE.indexOf(c) + offset) % 52; |
||||
return TABLE.charAt(position); |
||||
|
||||
} |
||||
|
||||
/** |
||||
* 解密轮盘 |
||||
* |
||||
* @param c 字符 |
||||
* @return 解密后的字符 |
||||
*/ |
||||
private static char decodeChar(char c, int offset) { |
||||
int position = (TABLE.indexOf(c) - offset) % 52; |
||||
if (position < 0) { |
||||
position += 52; |
||||
} |
||||
return TABLE.charAt(position); |
||||
} |
||||
// ----------------------------------------------------------------------------------------- Private method end
|
||||
} |
@ -0,0 +1,20 @@ |
||||
package cn.hutool.core.codec; |
||||
|
||||
/** |
||||
* 解码接口 |
||||
* |
||||
* @param <T> 被解码的数据类型 |
||||
* @param <R> 解码后的数据类型 |
||||
* @author looly |
||||
* @since 5.7.22 |
||||
*/ |
||||
public interface Decoder<T, R> { |
||||
|
||||
/** |
||||
* 执行解码 |
||||
* |
||||
* @param encoded 被解码的数据 |
||||
* @return 解码后的数据 |
||||
*/ |
||||
R decode(T encoded); |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue