diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml new file mode 100644 index 0000000000..98fd20cc34 --- /dev/null +++ b/.github/workflows/maven.yml @@ -0,0 +1,27 @@ +# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven + +name: Java CI with Maven + +on: + push: + branches: [ "master" ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up JDK 11 + uses: actions/setup-java@v3 + with: + java-version: '11' + distribution: 'temurin' + cache: maven + - name: Build with Maven + run: mvn -Dmaven.test.skip=true -B package source:jar deploy --file pom.xml -s mvn_settings.xml -DS_NUTZMAVEN_USER_ID=${{secrets.S_NUTZMAVEN_USER_ID}} -DS_NUTZMAVEN_USER_PWD=${{secrets.S_NUTZMAVEN_USER_PWD}} + env: + S_NUTZMAVEN_USER_ID : ${{secrets.S_NUTZMAVEN_USER_ID}} + S_NUTZMAVEN_USER_PWD : ${{secrets.S_NUTZMAVEN_USER_PWD}} diff --git a/.gitignore b/.gitignore index 387cca4572..7de83eb13b 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,6 @@ /build/libs .gradle local.properties -/mysrc \ No newline at end of file +/mysrc +*.iml +/local_conf diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index e46598991e..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,37 +0,0 @@ -sudo: false -language: java -script: mvn clean source:jar cobertura:cobertura package -jdk: - - oraclejdk8 -# whitelist -branches: - only: - - master - - coverity_scan -before_script: - - psql -c 'create database nutztest;' -U postgres - - cp ./tools/travis-ci/nutz-test.properties ./test/ - - cp ./tools/travis-ci/log4j.properties ./test/ -notifications: - email: false -before_install: - - export TZ=Asia/Shanghai -env: - global: - # The next declaration is the encrypted COVERITY_SCAN_TOKEN, created - # via the "travis encrypt" command using the project repo's public key - - secure: "E1z+6z9M4iTdAXZ2a1rYSrxfIOq6PkdXEMutAbIn/bp1e/Qvb5IVoAS0heo7SPwcIlHlN8mDiOtKdzbcu9q8VaftfHwFjff6AoKyuWtfDqE1ecTfflebWwzmtXKJmT5uxBPvu442dS4sIc2zx3zjvnxMsSmvrdSwbMxwdbAKvDc=" - - SONATYPE_USERNAME=wendal - - secure : "BaXmGpodQiuU23YgtUThWCHf7Vig2Gv3UfpBjo3FATgn1LRF3i2IOgY5sCSi+XJYqx+05fVNdwVYccxS/9UfhPNSqQuslIwgmg0y9f26DYaX2gaW+jk8padhZRkeBrY3fO+g9nQuu+Epgqi0ITru6+IjH932O0m1JR7iJu2RNhs=" -after_success: - - bash <(curl -s https://codecov.io/bash) - - python mvn_settings.py -addons: - postgresql: "9.3" - coverity_scan: - project: - name: "nutzam/nutz" - notification_email: wendal1985@gmail.com - build_command_prepend: "mvn clean" - build_command: "mvn -DskipTests=true compile" - branch_pattern: coverity_scan diff --git a/License.txt b/License.txt index 7f110e1a28..d26637e7a0 100644 --- a/License.txt +++ b/License.txt @@ -187,7 +187,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [2009-2016] [Nutz] + Copyright [2009-2021] [Nutz] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index 46ff9e4e06..ae65899518 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,8 @@

-[![Build Status](https://travis-ci.org/nutzam/nutz.png?branch=master)](https://travis-ci.org/nutzam/nutz) -[![Circle CI](https://circleci.com/gh/nutzam/nutz/tree/master.svg?style=svg)](https://circleci.com/gh/nutzam/nutz/tree/master) -[![Coverity Scan Build Status](https://scan.coverity.com/projects/4917/badge.svg)](https://scan.coverity.com/projects/4917/) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.nutz/nutz/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.nutz/nutz/) -[![codecov.io](http://codecov.io/github/nutzam/nutz/coverage.svg?branch=master)](http://codecov.io/github/nutzam/nutz?branch=master) -[![GitHub release](https://img.shields.io/github/release/nutzam/nutz.svg)](https://github.com/nutzam/nutz/releases) [![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) -[![Skywalking Tracing](https://img.shields.io/badge/Skywalking%20Tracing-enable-brightgreen.svg)](https://github.com/OpenSkywalking/skywalking) ## 项目目标 @@ -18,16 +12,22 @@ Nutz遵循Apache协议,完全开源,文档齐全,永远免费(商用也是) +完整的Mvc,Ioc,Dao,Aop, 快速开发Web/桌面/嵌入式应用,无强制依赖 + ## 项目各种资源地址 * [项目官网](https://nutzam.com) * [Nutz社区](https://nutz.cn/) 秒回, 就是这么爽 +* [NutzBoot](https://nutz.io) 可靠的企业级微服务框架,提供自动配置,嵌入式web服务,分布式会话,RPC等一篮子解决方案 * 在线文档 * [官网](https://nutzam.com/core/nutz_preface.html) Nutz手册,涵盖方方面面 * [w3cschool上的文档](http://www.w3cschool.cn/nutz/) [由vincent109维护](https://github.com/vincent109) + * [github pages](https://nutzam.github.io/nutz/) github 的 pages * [各种插件](http://github.com/nutzam/nutzmore) 您能想到的都有哦(基本上`^_^`) * [好玩的Nutzbook](http://nutzbook.wendal.net) 几分钟搭建一个demo有何不可? 入门从这里开始 * [在线javadoc](https://nutzam.com/javadoc/) 注释就是这么全 +* [NutzWk](https://github.com/Wizzercn/NutzWk) 基于Nutz的Java开源企业级开发框架 +* [Idea插件](https://github.com/threefish/NutzCodeInsight) idea 插件 ## Nutz生态系统 @@ -39,33 +39,22 @@ Nutz遵循Apache协议,完全开源,文档齐全,永远免费(商用也是) org.nutz nutz - 1.r.62 + 1.r.69.v20220703 ``` +详情: [https://nutzam.com/core/basic/maven.html](https://nutzam.com/core/basic/maven.html) -详情: https://nutzam.com/core/basic/maven.html - -## Gradle 依赖 - -```gradle -compile(group: 'org.nutz', name: 'nutz', version:'1.r.62') -``` - - -## Sponsorship -YourKit supports open source projects with its full-featured Java Profiler. -YourKit, LLC is the creator of [YourKit Java Profiler](http://www.yourkit.com/java/profiler/index.jsp) -and [YourKit .NET Profiler](http://www.yourkit.com/.net/profiler/index.jsp), -innovative and intelligent tools for profiling Java and .NET applications. +## 关于我们 -![YourKit Logo](https://cloud.githubusercontent.com/assets/1317309/4507430/7119527c-4b0c-11e4-9245-d72e751e26ee.png) +广州市文尔软件科技有限公司 -JetBrains IntelliJ IDEA +## Contributors -http://www.jetbrains.com +This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)]. + -## 关于我们 +## Stargazers over time -广州市文尔软件科技有限公司 +[![Stargazers over time](https://starchart.cc/nutzam/nutz.svg)](https://starchart.cc/nutzam/nutz) diff --git a/_build.gradle b/_build.gradle deleted file mode 100644 index 1c58cae1fb..0000000000 --- a/_build.gradle +++ /dev/null @@ -1,218 +0,0 @@ -apply plugin: 'java' -apply plugin: 'maven' -apply plugin: 'maven-publish' -apply plugin: 'signing' - -group = 'org.nutz' -version = '1.r.60' - -ext.isReleaseVersion = !version.endsWith("SNAPSHOT") - -description = "Nutz" - -sourceCompatibility = 1.6 -targetCompatibility = 1.6 - -repositories { - mavenCentral() -} - -dependencies { - testCompile group: 'junit', name: 'junit', version:'4.8.2' - testCompile group: 'com.h2database', name: 'h2', version:'1.4.181' - testCompile group: 'org.eclipse.jetty', name: 'jetty-server', version:'7.6.18.v20150929' - testCompile group: 'org.eclipse.jetty', name: 'jetty-jsp', version:'7.6.18.v20150929' - testCompile group: 'org.postgresql', name: 'postgresql', version:'9.4-1206-jdbc41' - testCompile group: 'mysql', name: 'mysql-connector-java', version:'5.1.40' - testCompile group: 'org.xerial', name: 'sqlite-jdbc', version:'3.8.11.2' - testCompile(group: 'com.alibaba', name: 'druid', version:'1.0.26') { - exclude(module: 'jconsole') - exclude(module: 'tools') - } - testCompile group: 'org.eclipse.jetty.aggregate', name: 'jetty-all', version:'9.2.20.v20161216' - testCompile group: 'org.eclipse.jetty', name: 'jetty-jsp', version:'9.2.19.v20160908' - compile(group: 'log4j', name: 'log4j', version:'1.2.17') - compile(group: 'javax.servlet', name: 'java.servlet-api', version:'3.1.0') -} - -sourceSets { - main{ - java.srcDir "$projectDir/src" - } - test { - java.srcDirs "$projectDir/test" - resources.srcDir "$projectDir/test-prop" - } -} -tasks.withType(JavaCompile) { - options.encoding = "UTF-8" - options.compilerArgs = ["-parameters"] -} - -javadoc.options.encoding = "UTF-8" -if (JavaVersion.current().isJava8Compatible()) { - allprojects { - tasks.withType(Javadoc) { - options.addStringOption('Xdoclint:none', '-quiet') - } - } -} - -task sourcesJar(type: Jar, dependsOn: classes) { - classifier = 'sources' - from sourceSets.main.allSource -} - -task javadocJar(type: Jar, dependsOn: javadoc) { - classifier = 'javadoc' - from javadoc.destinationDir -} - - -task androidJar(type: Jar, dependsOn: classes) { - classifier = 'android' - from sourceSets.main.output - exclude('org/nutz/repo/org/**') - exclude('org/nutz/dao/**') - exclude('org/nutz/trans/**') - exclude('org/nutz/service/**') - exclude('org/nutz/img/**') -} - -task jsonJar(type: Jar, dependsOn: classes) { - classifier = 'json' - from sourceSets.main.output - exclude('org/nutz/repo/org/**') - exclude('org/nutz/dao/**') - exclude('org/nutz/trans/**') - exclude('org/nutz/service/**') - exclude('org/nutz/img/**') - exclude('org/nutz/ioc/**') - exclude('org/nutz/mvc/**') - exclude('org/nutz/aop/**') - exclude('org/nutz/http/**') - exclude('org/nutz/net/**') - exclude('org/nutz/el/**') - exclude('org/nutz/repo/**') - exclude('org/nutz/filepool/**') - exclude('org/nutz/runner/**') - exclude('org/nutz/resource/**') - exclude('org/nutz/conf/**') - exclude('*.xsd') -} - -artifacts { - archives jar - archives sourcesJar - archives javadocJar - archives androidJar -// archives jsonJar -} - -processResources { - from ('src'){ - exclude '**/*.java'; - } -} -processTestResources { - from ('test'){ - exclude '**/*.java'; - } -} -test { - include "org/nutz/TestAll" - jvmArgs "-Dfile.encoding=utf-8" - //showStackTraces = true - testLogging { - debug { - events "started", "skipped", "failed" - exceptionFormat "full" - } - } -} - - -def Properties properties = new Properties() -properties.load(project.rootProject.file('local.properties').newDataInputStream()) - -//if (isReleaseVersion) { - ext."signing.keyId"=properties["signing.keyId"] - ext."signing.password"=properties["signing.password"] - ext."signing.secretKeyRingFile"=properties["signing.secretKeyRingFile"] - ext."ossrhUsername"=properties["ossrhUsername"] - ext."ossrhPassword"=properties["ossrhPassword"] -//} - - -uploadArchives { - repositories { - mavenDeployer { - beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } - - repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") { - authentication(userName: ossrhUsername, password: ossrhPassword) - } - - snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") { - authentication(userName: ossrhUsername, password: ossrhPassword) - } - pom.project { - name 'Nutz' - packaging 'jar' - description 'Nutz, which is a collections of lightweight frameworks, each of them can be used independently' - url 'https://nutz.cn' - - scm { - connection 'scm:git:git://github.com/nutzam/nutz.git' - developerConnection 'scm:git:git://github.com/nutzam/nutz.git' - url 'git://github.com/nutzam/nutz.git' - } - - licenses { - license { - name 'The Apache License, Version 2.0' - url 'http://www.apache.org/licenses/LICENSE-2.0.txt' - } - } - - developers { - developer { - id 'zozoh' - name 'zozoh' - email 'zozohtnt@gmail.com' - } - developer { - id 'wendal' - name 'Wendal Chen' - email 'wendal1985@gmail.com' - url "http://wendal.net" - } - } - } - } - } -} - -signing { - required { isReleaseVersion && gradle.taskGraph.hasTask("uploadArchives") } - sign configurations.archives -} - - -def installer = install.repositories.mavenInstaller -def deployer = uploadArchives.repositories.mavenDeployer - -//[installer, deployer]*.pom*.whenConfigured {pom -> -// pom.dependencies.find {dep -> dep.groupId != '' }.provided = true -//} - -deployer.pom.whenConfigured {pom -> - pom.dependencies.find {dep -> dep.scope == "compile" || dep.version == "2.5" }.each {dep -> dep.scope = "provided"} - pom.dependencies.find {dep -> dep.scope == "compile"}.each{dep-> println dep.scope } - pom.dependencies.find {dep -> dep.scope == "compile"}.each {dep -> dep.scope = "provided"} - pom.dependencies.find {dep -> dep.scope == "compile"}.each{dep-> println dep.scope } -} -installer.pom.whenConfigured {pom -> - pom.dependencies.find {dep -> dep.scope != "test" }.each {dep -> dep.scope = "provided"} - pom.dependencies.each{dep-> println dep.scope } -} diff --git a/_gradlew b/_gradlew deleted file mode 100644 index 9aa616c273..0000000000 --- a/_gradlew +++ /dev/null @@ -1,169 +0,0 @@ -#!/usr/bin/env bash - -############################################################################## -## -## Gradle start up script for UN*X -## -############################################################################## - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -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 -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn ( ) { - echo "$*" -} - -die ( ) { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - -# Determine the Java command to use to start the JVM. -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 - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=$((i+1)) - done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") -} -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" - -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]]; then - cd "$(dirname "$0")" -fi - -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/build/build2.xml b/build/build2.xml index c2462064af..dc7c229883 100644 --- a/build/build2.xml +++ b/build/build2.xml @@ -34,7 +34,7 @@ - + diff --git a/build/java-build b/build/java-build old mode 100644 new mode 100755 diff --git a/circle.yml b/circle.yml deleted file mode 100644 index 13060889e9..0000000000 --- a/circle.yml +++ /dev/null @@ -1,22 +0,0 @@ -machine: - timezone: Asia/Shanghai - java: - version: oraclejdk8 - -general: - branches: - only: - - master - -dependencies: - override: - - cp ./tools/travis-ci/nutz-test.properties ./test/ - - cp ./tools/travis-ci/log4j.properties ./test/ - -database: - override: - - psql -c 'create database nutztest;' -U ubuntu -test: - post: - - mkdir -p $CIRCLE_TEST_REPORTS/junit/ - - find . -type f -regex ".*/target/surefire-reports/.*xml" -exec cp {} $CIRCLE_TEST_REPORTS/junit/ \; diff --git a/demo/nutzdemo/pom.xml b/demo/nutzdemo/pom.xml index b813c168d4..a9109bf60e 100644 --- a/demo/nutzdemo/pom.xml +++ b/demo/nutzdemo/pom.xml @@ -14,7 +14,7 @@ junit junit - 4.12 + 4.13.1 test diff --git a/demo/nutzdemo/src/main/webapp/WEB-INF/web.xml b/demo/nutzdemo/src/main/webapp/WEB-INF/web.xml index e9b5125dc9..e6ead89bb4 100644 --- a/demo/nutzdemo/src/main/webapp/WEB-INF/web.xml +++ b/demo/nutzdemo/src/main/webapp/WEB-INF/web.xml @@ -6,7 +6,7 @@ dummy - Nutz Web Demo + NutzWebDemo nutz org.nutz.mvc.NutFilter diff --git a/doc/ci/logo.ai b/doc/ci/logo.ai new file mode 100644 index 0000000000..05fb5f2b28 --- /dev/null +++ b/doc/ci/logo.ai @@ -0,0 +1,661 @@ +%PDF-1.5 % +1 0 obj <>/OCGs[5 0 R 6 0 R 40 0 R 41 0 R 77 0 R 78 0 R 127 0 R 160 0 R 193 0 R 194 0 R 229 0 R 230 0 R 265 0 R 266 0 R]>>/Pages 3 0 R/Type/Catalog>> endobj 2 0 obj <>stream + + + + + application/pdf + Adobe Illustrator CC 22.0 (Macintosh) + 2017-12-13T00:30:52+08:00 + 2018-03-02T01:30:37+08:00 + 2018-03-02T01:30:37+08:00 + + + + 232 + 256 + JPEG + /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgBAADoAwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9Befda1TSrC3ksJBEZZC kjlQxHw1FOVR49sy9JijMm3G1OSUQKeaXes6tdkm5vJpa9mdiv0LWgzaRxxHIOulkkeZQeTYIm21 LUbUg211LCR09N2X9RyMoRPMMhMjkWRaX+Y2uWpC3fG9hHXmOD09mUfrBzFyaKB5bORDVyHPdneh +atI1heNvJ6dxSrW0lFf6OzD5Zr8unlDnyc7HmjPknGUtrsVdirsVdiriQBU7AdTiqCn1vRrckT3 1vGw/ZaVAfurXLBikeQLA5IjmQg385eWENDqEZ/1QzfqByY02TuYfmId6j/j3yn/AMt3/JKb/mjJ flMnd9yPzOPvXp538rPSl+or4pIv61GA6XJ3J/MQ70VD5l8vzf3eo29T0BkVT9zEZA4JjoWQzQPU JhHLHIvKNw6/zKQR+GVkUzBXYEuxV2KuxV2KuxV2KuxV2KuxV2KsU/MqH1PLqv8A76nR/vDL/wAb ZmaI+v4OLqx6Pi8szbOsdirsVdiq5JHjdXjYo6mqspoQR3BGJFpBeieUPPn1ho9P1ZgJj8MN0dg5 7K/gffvms1Gkr1Rc/Bqb2kzjMBzUPe6hY2MXq3c6QR9i7AV+Xj9GSjAy5BjKQjzYrqf5m6XDVLCF 7tx0kb93H+ILfgMzIaGR57ONPWRHLdjN/wDmF5juqiKRLVD+zCorT/WbkfuzKho4DzcaWqmfJIbr Ub+7JN1cyzk/78dm/WcyIwA5BolMnmUPkmLsVdirsVdiqpBcXED84JXif+ZGKn7xgIB5pBI5J5Ye e/MlnQfWfrMY/YnHOv8Astn/ABzHnpMculN8dTMdWVaV+ZunzcU1GBrVzsZU/eR/Mj7Q/HMTJoZD 6Tbkw1gPPZltnfWd7CJrSZJ4j+0hBp7GnTMOUDE0Q5UZA8lfIsnYq7FXYq7FXYq7FXYqkXniH1fK 18O6qjj/AGLqf1DMjSmsgaNQLgXj2bp1LsVdirsVdirsVZLD5/16HS0sY3X1E+EXbDlJw7Dfao8T mKdJAytyRqpCNMfuru6u5jNcyvNK3V3JY/jmTGIAoOOZE7lSwodirsVdirsVdirsVdirsVdirsVV 7O+vLKYT2kzwSj9pCR9B8RkZQEhRZRkQbDONB/Ms1WDWU26fW4h+LoP+NfuzAy6LrFzcWr6SZ3bX NvcwJPbyLLC4qkiGoOa+USDRc0EEWFTAl2KuxV2KuxV2KqN7aQ3lpNaTV9KdDG/E0NGFNjkoyMTY RKNiildp5M8s2wHCxSQ92lrJX6HJH4ZbLU5D1ao6eA6I39B6Lx4fo+24/wAvox0+6mQ8WfeWfhx7 gg7ryd5auQQ9hGhPeKsdP+AIGTjqcg6sJYIHoxrVfywWhfS7kg9fQn6fQ6j9Y+nMrHrv5wceej/m lhGoadfafcG3vIWhmG/Fu48QRsR7jM+ExIWHClAxNFDZJi7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FU00LzHqWjXHqWr1iY/vbdt0cfLsfcZVlwxmN23HlMDs9Y0HX7HWrP6xbHi67TQt9pG9/Y 9jmny4TA0XaYsomLCZZU2OxV2KuxV2KuxV2KuxV2KsY82edLfSFa1taTaiR9k7rGD3f38BmXp9MZ 7n6XGz6gQ2HN5dd3l1eXD3N1K008hq7san+we2bWMREUHWykSbKjkmLsVdirsVdirsVdirsVdirs VdirsVdirsVdirsVdiqN0fV7zSb5Lu1ajLs6H7Lr3VvY5XkxiYos8eQxNh7LpGqW2qWEV7bn4JBu p6qw6qfcZpMmMwlRdvCYkLCMyDN2KuxV2KuxV2KuxVinnXzeulxGxs2rqMi7uNxEp7n/ACj2H05m abT8e5+lxdRn4dhzeXSSPI7SSMXdyWd2NSSdySTm1Ap1pK3Ch2KuxV2KuxV2KuxV2KuxV2KuxV2K uxV1cVaqMVbrirsVdirsVZJ5K8ynSNQ9Gdv9AuSBNXojdBJ/X2zF1WDjFjmHI0+bgO/IvWgQQCDU HcEZp3auxV2KuxV2KuxVJPNnmOLRNOLrRryaq20Z8e7H2XL9Ph8SXk058vAPN4/NNLPM80zmSWQl ndjUknck5ugABQdSTe6zCh2KuxV2KuxV2KuxV2KuxV2KuxV2KuxVommKus7a91G+jsbCMzXMp+FR sAB1Yk7ADK55BEWWyEDI0Eb5k8p+YfL1qt7erHJaEhXlgYsEZugcMFO/j0ynHqoyNBtnp5RFlJ7W 7WXocyAWghGDJMXYq7FU20HyzqetTcbdOECmkly9Qi+w/mPsMpy54wG/NtxYZT5PXtKsP0fp8Fn6 rziBQgkf7Rp/AdB7Zpsk+KRLtoR4RSKyDJ2KuxV2KqdxcQ20ElxMwSKJS8jHsqipwgEmggmhZeL+ Ydan1jU5bySoT7MEf8kY6D+J983mHEIRp0+XIZytLcta3Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY qoXE4RTv2wEpAekeUNNtPKnlq58w6x+7nlj9SQEfEkX+64gD+25I28aDtmk12qAsn6Yu40WlJIA+ qSn+b+tyWP5dMLlRHeai9vb+mNwshImcCvYLGwyOlskE806mgCByeQaFM7jfxzcwLqphkK9Mta1y qWIVRUnYAdScUM38s/l5LPwu9YBih6raDZ2/1z+yPbr8swM+sA2j83Nw6W95M/thZQUs7f04/RUE W6UBVTUA8R0BpmtJJNlzwANgr4EuxV2KuxV2KsF/MvXDHDFpELUaYCW5I/kB+BfpIr9GbDQ4rPEX C1eTbhed5snXuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxVa7ACuKU+/Lzy1+mtYN/cpXTrBgaHpJN 1VPcL9pvo8cwdXm4RQ5ly9Nis2eQRPmjzAfNX5iaV5Ws256XYXQlvSu6yyQVeQH/ACUVSvzJ9s5P Pl8bNHGPpB3eu02DwNPLKfqkNvikH/OQ+sG48waNocbfDaxPdzgfzTNwSvuBG3350Wmj1eZ1BY3o UJVPpzZwDr5FlekaNqOq3It7KIyNtzc7Ig8WbthyZIwFlYYzI0Hp3l3ydpmiR/WZiJ7xRye5fZU2 34A9Pmd81WfUyntyDscOnEN+rGvN/wCa0MHOy0AiabdXviKovb92P2j/AJR2+eYzkMA0XzRqmma6 mr+q885b/SebEmVG+0rE169vA4q9+0zUrTU7CC+tH52868kbv4EH3B2OKonFXYq7FVskiRxtI54o gLMx7ACpOEC1JeIa1qUmpapc3r/7uclR4INlH0KBm9xQ4YgOlyT4pEoLLGDsVdirsVdirsVdirsV dirsVdirsVdirRNBiq20sbvVb+HTrMVnuG4g9lHVmb2UbnKskxEWWzHAyNBnvnrXrTyL5Qg0fSm4 6hcoYrdv21H+7bhv8ok7e/yzlu0tYYj+lJ6nsrQjJLf6IsV/ITSTNrOo6q4qLWFYYyf55mqSPksf 45ruysdzMu4fe7TtrLUIw7z9zAvOGq/p38ydavVPKFLg20B7cLYCEEezcOX0512njQeNzy3ejeR/ IV5qSrcXdbaxJqP9+OP8kHoPc5Zl1QhsNywx6cy3PJ6Rfal5b8o6Wok428QB9K3TeWVh1oDux8Sf vzWTmZGy58YCIoPJfNnn7VtfdoQTa6dX4bVD9qneRtuXy6ZFkxjFXYqz78qvNRsdQ/Q109LS9atu T0Sc7AfJ+nzpir2DFXYq7FWO+fdQNn5cnVTSS6IgX5Nu3/Cg5k6SHFMeTj6mVQ97yPNy6p2KuxV2 KuxV2KuxV2KuxV2KuxV2KuxV2KqFzNwQnASkPRPI2k2vlny7c+ZdY/dTSxGVqj4o4OqoB/NIabfI ZpdbqQLJ+mLt9FpiSAPqk8U81eZL3zFrlxql1sZDxhirURxL9hB8h18TU5xefMckjIveabTjFARD 17yFy8s/lLea1x/0mWO4vY1I3LhfTgXf+YotPnm+7Mx8OIE9S812vl4sxA/hFMH/ACp8l2yvHeX/ APpFxXkFbdQetTX7R+ebaec8g6eOEcy9B8xfmXY6VG9lpHG7vhUNN1hjPzH229ht+rKG55dqGo32 o3T3d9M09xJ9qRzU08B4AeAxVD4q7FXYq2rMrBlJDA1BGxBGKvfPI3mQa9oMVxIf9Mg/c3Y/y1H2 v9kN8VZDirsVeefmneE3FjZA7IjTMPHkeK/8RObLQR2JcDWy3AYJmwcF2KuxV2KuxV2KuxV2KuxV 2KuxV2KuxVZI4Va4pTzyF5Z/T2rfWrleWmWJDSg9JJeqx+47t7bd8wtVm4RQ5lytNi4jZ5IH85/O /wCkL/8Aw9YyVsrJ63jr0knH7Pyj/wCJfIZxnaWq4pcA5D73t+ydHwR8SXM8vd+15pbwS3FxFbxD lLM6xxr4sxoB95zVgWadxKQAsvcfzavrHy95CsNGMojilMVuB3aK2TkaAbk8lTOvxw4YgdzwuSZn IyPUvKrfzVf3Vr9UtK2tkRRgppJIP8ojoPYfjk2C5BQYquxV2KuxV2KuxVlf5b+YTpHmKKORqWd9 SCcE7Bif3b/Q23yJxV7nirsVeRefrr1/M9yAarCEiX6FBP8AwxObnSRrGHVak3MsezJcd2KuxV2K uxV2KuxV2KuxV2KuxV2KtE0xVZBa3epX8GnWa87m4YIg7DxY+yjc5XkmIiy2QgZGg9B8465Z+QPJ 0Om6cw/Sdwpjtm/a5EfvbhvlXb3p2Gcv2jrDEX/FLk9P2XofElX8Eeb5+ZixLMak7knqTnLvYsv/ ACo0n9JeedPDLWKzJu5Pb0RVD/yMK5l6DHxZR5buB2nl4MEvPb5/sa/5yH1c6j58tdKRuUWk2g5r /LNc/vG++MR507x7H9Hh4RLiqajFW8VdirsVdirsVdir6A8j67+mvLlrdO3K5jHoXPj6ke1T/rCj fTiqfYq8O1ucz6zfTf78nkYfIuafhm+xCogeTpchuR96Cyxg7FXYq7FXYq7FXYq7FXYq7FXYq7FV C4lVF3wEpD0TyHotpoGi3HmfVyIZJIi4LdYrYfF0/mk2P3DNPrtSN/5sXa6PTkkAfVJ4x5w8zXXm TXrjU56qjnhbQk1EcK/YX+J9yc4rUZjkmZF73S6cYYCI/BSXKXJex/kDpQWLVdYkFASlrE57BR6k u/0pm57Jx/VL4fj7Hn+28v0w+P4+14nqurt5g836xrJbkl5dSvCf+KgeMQ+iMKM3LoGQWICxDFUX zGKu5jFW+QxVvFXYq7FXYq9A/J/Wjb6vPpUjfur1OcQP+/Yt9vmlfuxV69irwJ2LuzHqxJP050To 2sUOxV2KuxV2KuxV2KuxV2KuxV2KrWNMUsl8peQrnU9Qiu9TX07GAiQ27D4pCN1Vh2U9/bNfqNUO UXNwac85JZ+dXnYXd2PLdjJ/o1o3LUGU7PMPsx7do+/+V8s5DtLU8R4ByHP3vZ9kaPhHiS5nl7v2 vK81Tu3Yq+hvKXlzUI/ynGm6e6W2qanZzPFPJUKkl2p9N24gn4UZe3bOn0OPhxDz3eO7Ry8eaXlt 8nmWm/8AONvm60WjarYH5et/zRmW4Kbj8i/NyR0TUbFmHQEzAff6ZxVr/lSPnf8A5bdO/wCRk/8A 1RxVa/5KeeEAIurCQ+CyS/8AG0S4qxDVbHU9E1OTTdUjMN1HQ06qyt0ZWGxU4qujfkK4qqYq7FXY qjNH1GTTdVtL+P7VtKslB3APxL9I2xV9IQyxzRJLGeUcih0bxDCoOKvA86J0TsVdirsVdirsVdir sVdirsVdiqK07S77UrkW9nEZZD1p9lR4segGQnkERZZwgZGgznSfKNrpbIZALrU3+yx+wjf5APh/ MfwzVZ9UZ7DYOxw6cR3O5RHn/wA1ReUPLJW3YHVLusdoD15kfHMR4IDt70GanW6nwobfUeTuOz9J 42Tf6RzfOLu7uzuxZ2JZmY1JJ3JJOcy9eBTWKXYqnkfnnzjFGsces3aRoAqIJnAAAoAN8uGpyD+I /Nxjo8J34R8ldPzF88KoUa1c0Hi1T95BOS/N5f5xYnQYf5ob/wCVkeef+rzcfeP6YfzmX+cUfkMH 80Kw/NLz8AANYk223SI/rTD+ezfzmP8AJuD+b970T8pNU88eYLubUtU1KWTSbWsaxlI1E0xHSoUG iA1ND1p75sez55ch4pH0h1PaePDiAjGPqP2B5x+bfm+z1/z3IunkPa6YgsvXG/qOjO0hB/lDOVHy r3zbukQVkSYxXFUTireKuxV2Kvefy51I3/lGyLGslsDbP7ekaL/wnHFXlt7CYL24hIoYpHQjp9li M6CJsAukkKJChkmLsVdirsVdirsVdirsVdirJfLXkm/1fjcT1trDr6pHxOP8gH9Z2+eYufVCGw3L k4dOZ7nYMr1nXvLHkrTPRUBZWFY7aP4ppD/MxPb3P0eGarJkMzZdjCAiKCh5Rub2XTrjzf5hYW0U kbSWdt+xBagV9Q92eTx8OlKkZVKQiCTyDbCBkQBzLw/zp5quvM2vT6jNVYf7u0hJ/u4VPwj5nq3v nLanOcszIvZ6TTDDARHx96RZQ5TsVdirsVdirsVTDQNEvdc1e20uzFZ7l+PI9FUbs7eyrU5Zixmc hEdWrPmGOBkeQesfmv5ptPy+8j2vlnQm9PVL+M29sy7PHF0nuWp+2xai/wCUaj7OdVixCEREdHis 2WWSZkeZeE6HppWNCR3GWNTLII+KAYqrYq7FXYq7FXqX5L35MOpWDHZWjnjH+sCj/wDEVxVJ/ONo bbzLfpTZ5PVB8fVAf9bZu9NK8YdRqI1MpLl7S7FXYq7FXYq7FXYqq21rcXU6QW8bSzOaJGgqTglI AWUgEmg9E8ufl/a2Si91krLMo5+gSPSjpvVz0Yj7vnmsz6wnaPJ2GHSgbySvzj+a8Fqr2WgFZZR8 LXxAMa/8YwftH3O3zzBcxhnkXy9c+b/Mj3GoM81jasJr+VyWMjH7EVT/ADU39sVTL86vOolmHlfT 3pbwFW1Bl6M43SLbsnU+9PDNH2nqbPhj4vRdkaOh4suvL9byfNS712KuxV2KuxV2KuxV7h+W2h6f 5O8pXfm7XD6MksHrMzD4orYbqoH80poaf6o65v8As7TcEeM85fc8v2rq/EnwD6Y/e8D1/XNR84+a 7rXr4EG4IFvDWoigUkRxj5Dr4mp75s3UJ/YWaxxrtiqYAUGKt4q7FXYq7FWZ/lLeeh5tWGu11BJF T3Wkn/GmKsl/NHTyl5aX6j4ZUMLkfzIaivzDfhmz0M9iHX6yO4LB8z3CdirsVdirsVdiqc+X/K2p 61L+5X0rVTSS5cfCPZf5j7DKM2eMOfNuxYZT9zPj/hbyTpvqzOFlcU5GjTzEdlHh+A75qcuaUzu7 LHijAbPL/Nvn/VtfZoQTa6dX4bVD9r3kb9r5dMqbWLJbz3dzBZ268ri5kSGFelXkYKor8zir1/WL 3T/y18jR2loVfVJ6rCSN5LhgPUmYfyp/zSMxNZqfChf8R5OboNIc06/hHN4BLLLNK80rmSWRi8js aszMakknuTnME29iAAKC3FLsVdirsVdirsVZt+Vfkv8AxFronuk5aVp5WS5r0kfqkXvWlW9vmMzd DpvEnv8ASHW9pavwoUPqly/W7/nILz+2q6unk3S5K2Ng4fVXXo9wN1iqOoiB3/yv9XOleSYTpFgU Ck+GKshRaCmKr8VdirsVdirsVTzyPc/V/NulSVpyuFj/AORv7v8A42xV7R5x0k6noNxCi8poh60A 78k3oPmtRl+mycMwWnPDiiXjebt1DsVdirsVbRGdgiAszGiqBUknsBilnfln8u3fhd6yCidUswaM f+MhHT5DfNfn1nSPzc3DpeslXzT+ZWl6PEdO0NI7i6jHAOtPQhptQU+0R4Db9Wa4m+bnAU8n1HUr 7Urt7u+mae4k+07nt4AdAB4DAlCnFViXlzYXdvfWxpPayJNET05RsGFfbbFXo/8A0MTocUCm60e8 E9P3ixGJ0B/yWZkJH0Yqhz/zk35YBp+htR+6D/qpirX/AEM55X/6s2o/dB/1UxVpv+cnvKwFTo2o /dB/1UxVn3k7z9YeZ/K83mSK1nsrGJpRxuOHMrAtXccCwp1HXtkZSEQSejKMTIgDq+a9QvJb6/ub 2b+9upXmk/1pGLH8TnISkZEk9XuoQEYiI6KGBmiNPsLrUL6CxtIzLc3LrHEg7sxoPo8TkoRMiAOZ YTmIRMjyD2vzZrNj+VP5cx2lkVfWroGK0O1ZLp1/eTkEfYj9/wDJXvnU6fAMUBEPF6rUHNMyP4D5 20SzllneaasksjF5JGNWZmNSST1JOXuOzG2hCKNu2KojFW8VdirsVdirsVRmjSmHWLGUGhjuInB6 fZcHrir6SxV5B500Q6VrUgRaWtzWaCnQAn4l/wBifwpm602Xjh5h1Oox8MvIpBmQ0OxVH6Poeo6v c+hZxcqf3kp2RB4s3+ZyvJljAWWzHjMzQeiWWj+W/J9ib/UJlNwBQ3Mg+Imn2Yk3P3b5qc2plPyD ssWAQ97z/wA3/mTqWs87Sx5WWmnYqDSWQf5bDoP8kfTXMdvYZirsVdiqx0DChxVAT6fG/Va4qhTo 0JP2fwxVr9Cw/wAv4YqhrzSoVjJ44q901mNfKf5J2+nIPTnntorZl6fvbr95OPuZ8wu0MnDiPns7 DszFx5x5bvDc5p692KvZfyc8q2+madP5w1crCnpubR5fhWOBQfVmJPTkBQe1fHN32ZpqHiH4PO9r 6uz4cenP9Txvzv5wuPPXnGfVCGXToT6GlwHbjAp2Yj+aQ/E33ds27okbpdosa9KYqmoGKt4q7FXY q7FXYq7FV8DhJo3borAn5A4q+m8VSfzXoKazpTwKALqP95bOezj9knwbpl+ny8Er6NObFxxrq8bk jkikaORSkiEq6nYgjYg5ugbdSRTK/LPkG81Djc6hytbM7qnSSQewP2R7nMTPqxHaO5crDpjLc7BO /MPnfQPKtsdN0uJJr2PYW8f93GfGVh1Pt18aZq5TMjZdhGIiKDybWdc1TWbw3eoTmaToi9FRf5UX oBkWSAxV2KuxV2KuxVqgxV3EYq1QYqq6Jpg1XzJpmnceSXFxGso/4rB5Sf8ACA4qzn8/9W+LStIU 9A93Kvz/AHcZ/wCJ5pe1sn0x+L0HYmL6p/D8fY8fzTu/ZL+X/lCbzP5gitCCLGCkt/KO0YP2Qf5n Ow+/tmTpNOcs66dXD12qGHHf8R5Ml/5yD89RwW0XkPR3EdY0fVfTIASECsVtt05CjMP5adic6gAA UHjSSTZeX6BpqoimmFDJYowo2xVVxV2KuxV2KuxV2KuxVdEnqSolac2C1+Zpir6cxV2KpW/lnRn1 Y6q9uHuiB9rdOQ/b4/ze+XePLh4b2avBjxcVbpplLa8f/M7yWdPuW1mxT/Qbhq3MY/3VKx6/6rn7 j9GKsBxV2KuxV2KuxV2KuxV2KrWNBirMfya07615tuL5hVNPt24nwkmPBf8AhOeKsa/NbVv0l551 Eqax2jC0j9vRFHH/ACM5ZzGuycWU+Wz2HZmLgwR89/mxOKKWaVIYkMksjBI0UVZmY0AAHcnMQC3O JAFl7qJdO/Kf8t5b+6VJNXmAJjr/AH15ID6cII34Rjr7Bj3zp9Hp/ChX8R5vHa7VHNkv+EcnzpZp fapf3Gp6hI097eO01xM3Vnc1J/szLcJldjbCOMCmKowDFW8VdirsVdirsVdirsVRWlRetqlnFSvq TxpTp9pwMVfSmKuxV2KuxVTuLeC5gkt50EsMqlJI2FQykUIOKvC/PHk248u3/KMNJpk5P1aY78T1 9Nz/ADD8R9OKsZxV2KuxV2KuxV2KuxVSmagxV6x+UsUWk+StS124X4ZXlnJ6VhtUI/4kHyM58MSe 5njgZSER1Lwy5uJbm5luZjylmdpJG8Wc1J+85yBNmy91GIAodHqX5KeS1uLhvM1+n+j2pKWCt0aU fal37J0Hv8s2vZmms8Z5Dk6XtfV0PDjzPN5z+bfniTzv5s4WchbQtLZobDifhlb/AHZP/syPh/yQ PfN484o6RY+nEK+GKpwi0GKrsVdirsVdirsVdirsVdiqd+SbY3Hm3Sox+zcJJ/yKPqf8a4q+g8Vd irsVdirsVQup6ZY6nYy2V7EJbeUUZT1B7EHsR2OKvCPN3lK+8u35hlrJaSkm1uQNnXwPgw7jFUix V2KuxV2KuxV2KoS8ei4q9f8APDf4b/KSDTB8FxPFBZt2+N/3k/38X+/MHtHJw4j57Ox7KxcWYf0d 3j/lLy1d+Y9dt9Lt6qJDyuJQKiOFT8bn5dvegzQ4MJyTEQ9NqtQMUDI/gvQvzz8523lbyxbeSdBP o3l9CI5Ah3hshVWNR+1MQV+XLvTOqhARAA5B4vJMzkZHmXjOh6cVjSqjrkmDKoI+KAUxVWxV2Kux V2KuxV2KuxV2KuxVmf5S2fr+bFmptaQSS192pH/xvir2vFXYq7FXYq7FXYqg9X0iw1awksb6MSQS D6VPZlPZhirw7zh5Nv8Ay7eUYGbT5W/0a6pse/F6dGH49sVY9irsVdirsVaOKoryzpp1XzdpNjTk j3CPKvjHF+8f/hUOKst/P7Ved7pekof7qN7qUDuZDwT7uDffmk7WybiPxei7ExemU/gnflLT9N/L nyHd+ZNbHC8liE1wn7YB/ubZK/tMWFf8o77DMzQabw4WfqLr+09X4s6H0x/Fvni71LUfMvmK61zU yXu71/UYD7KL0RFr+yigKMz3WsnsrZUjXbFUcBireKuxV2KuxV2KuxV2KuxV2KvVfyX08ra6jqDD +8dIIz/qAs3/ABNcVek4q7FXYq7FXYq7FXYqoX1jaX1rJaXkSzW8o4yRt0I/gfcYq8a86/l3e6Gz 3lkGudKJqW6vDXtJTt/lffirDcVdirsVWsdsVZr+Smm/WfM19qLCqWNuI1PhJO2x/wCBRsVTXStA /wAWfmfqmu3SctJ0ecW8CndZJ7cBAo8VVgXPzHjmox4vGzymfpifu/Fu8y5/A00YD6pi/gfxTzn8 9PPh8zeZl8u6fLy0fR5CJmU7TXYqrt8o90Hvy9s27o2PaTp6oFNOwxVPEUAUxVfirsVdirsVdirs VdirsVdirsVe/eQtLOm+VLCFhSWVPXl8eUp5ivuFIGKsgxV2KuxV2KuxV2KuxV2KtMqspVgGVhQg 7gg4q8285flYkvO/0BQkm7SWHRW/4xE9D/k9PDFXl00M0ErwzI0UsZKvG4KspHUEHpiqzFVOQ7HF XsP5NWBt/J893Fxa4vbiV1LdP3YEaK1O3JSfpwHySOe6W/mf5pg/L7yPDoukSH9NagrRW0u3qLyN bi6b/KJY8f8AKPgMhixCERENmbKckjIvn3QdKo3IjevXLGpl8EIRRt2xVEYq7FXYq7FXYq7FXYq7 FXYq7FUz8s6SdX16y0+hKTSD1ado1+Jz/wACDir6LAAAAFAOgxV2KuxV2KuxV2KuxV2KuxV2KuxV j/mnyTo/mGItOno3wFIryMfGKdAw/bX2P0UxV455l8n615fmpdxc7ZjSK7jqY28AT+yfY4qkMgqD iqM0Dz95p8qpLDpjRS2src2trlC6ByKFl4sjCoHjirF9fvtd8za4+raxN61ywCIFHFI416Iijooq f1nfFUbYWfpDFUxAxVvFXYq7FXYq7FXYq7FXYq7FXYq9P/JzQzW71qVdv95rYn6GkP8AxEffir0/ FXYq7FXYq7FXYq7FXYq7FXYq7FXYqsmhhnieGZFliccXjcBlIPYg4q898zflHaXBe50OQW0p3NpI SYj/AKrbsvy3HyxV5drHl/VNLuDBqNq9vJXYsPhandWHwt9BxVALbqDWgxVVC0xVdirsVdirsVdi rsVdirsVdirsVVbW2murmK2gXnNO6xxqO7MaAYq+itC0mHSNItdOh3W3QKzdOTnd2/2TEnFUfirs VdirsVdirsVdirsVdirsVdirsVdirsVUbyys723a3vIUuIH+1HIoZfuOKsE138oNLueUukTmylO/ oSVkiPyP21/HFWA6x5E80aUWaeyaWFf932/71KeJ4/Ev+yAxVIMVdirsVdirsVdirsVdirsVdir0 b8ovLfr3cuuXCVit6xWlR1lI+Jh/qqafT7Yq9YxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2 KuxV2KpVqvlXy7qtWvrCKWRusoHCT/g04t+OKsT1H8m9GlJaxvJrVj0VwJUHy+w344qxy9/J/wAy Qkm1mt7pB0AZo3P0MOP/AA2KpJdeQfOFtX1NLlaneLjL/wAmy2KpZPouswV9ewuIqVrzidenXqMV Qjo6GjqVPgRQ/jirWKtojuaIpY+AFT+GKouDRdZnp6FhcS1pThE7denQYqm2n/l/5svJo0/R8sEb sA8sw9MICd2IYg7e2KvcdJ0y10vTrewtV4w26BF8SepY+7Hc4qi8Vdir/9k= + + + + xmp.did:cc904e27-b1e9-4a82-85b6-1dec225b3b18 + uuid:48076d9b-88a3-e040-acba-5e72522feb27 + uuid:8DA34F1C6409DD11A85196CD1A741DD0 + proof:pdf + + xmp.iid:828A06EB12B7DF11BCB1D5A43C1C7911 + uuid:8DA34F1C6409DD11A85196CD1A741DD0 + uuid:8DA34F1C6409DD11A85196CD1A741DD0 + + + + + saved + xmp.iid:A2A3E3F70DB7DF11BCB1D5A43C1C7911 + 2010-09-03T11:47:26+08:00 + Adobe Photoshop CS4 Windows + / + + + converted + from image/jpeg to application/vnd.adobe.photoshop + + + derived + converted from image/jpeg to application/vnd.adobe.photoshop + + + saved + xmp.iid:A3A3E3F70DB7DF11BCB1D5A43C1C7911 + 2010-09-03T11:47:26+08:00 + Adobe Photoshop CS4 Windows + / + + + saved + xmp.iid:cc904e27-b1e9-4a82-85b6-1dec225b3b18 + 2017-12-13T00:30:54+08:00 + Adobe Illustrator CC 22.0 (Macintosh) + / + + + + 3 + sRGB IEC61966-2.1 + False + 1 + 720000/10000 + 720000/10000 + 2 + 1 + 256 + 256 + 1 + True + False + + 256.000000 + 256.000000 + Points + + + + Cyan + Magenta + Yellow + Black + + + + + + 默认色板组 + 0 + + + + Document + + + + + + + + + + + + + + + + + + + + + + + + + endstream endobj 3 0 obj <> endobj 8 0 obj <>/Resources<>/ExtGState<>/Properties<>/XObject<>>>/Thumb 275 0 R/TrimBox[0.0 0.0 256.0 256.0]/Type/Page>> endobj 268 0 obj <>stream +HnEoq D FV Hޟvqw}IRm\Rw?Ͽ~zL=/wRϒwsYkv5#YnΙ'wNך8ke=B{$sM'WolxʾLliw3X%o1IFcE$NY>#qkepa,,ɢ<§ai{6JFxK[ď᪣)ɵZUR\Fy-.(0+5+4ExyoI;b+LJH D~) b CW+o:\=]NH\DtyKڶɒ_kKz$a E lI$-{j`G3m@ԚPހ䖭Kk3pwOBMbnh1 [Ƃ_ڕ.^cj"FY`vĔDq 975ƄLSĕv )PfY:Ui%\,HYq{WA_M!q[14ufzuVSڵaXbQr[3]?.L'6TZP5SI;_Ws[#NPs:bŧq}İGZ焷KfE̬x`Q0_kQ׻pQo e,$rO][bΘd.1YM'Q <(K `/n@> endobj 275 0 obj <>stream +8;W"^gD.UK$q%MbP9JSoHmM.)0!A2`%":HGEbH)=E[=?MGP(a]?TQAKIJ?+r4:cqD +Gm=:q\IM#'o1Y8.f*(qfnj2DFM#D16=b1$$,'g7(!4O-#=dJC-Ik6,196VT8IapTp +MnTh&hpI:LlB]65LQ'l)qDH'cO8UWP(OQ=]YIo\>T^2_H[_-J)\"CdKn5sG4kfZcN +XU`s4T--F>`:`Kr,4Z4'rVOgpko8No5aFS/BBMO@IZ[n/&2/dJiDYei*k#> endstream endobj 277 0 obj [/Indexed/DeviceRGB 255 278 0 R] endobj 278 0 obj <>stream +8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0 +b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup` +E1r!/,*0[*9.aFIR2&b-C#soRZ7Dl%MLY\.?d>Mn +6%Q2oYfNRF$$+ON<+]RUJmC0InDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j$XKrcYp0n+Xl_nU*O( +l[$6Nn+Z_Nq0]s7hs]`XX1nZ8&94a\~> endstream endobj 274 0 obj <>/ExtGState<>/ProcSet[/PDF/ImageC/ImageI]/XObject<>>>/Subtype/Form>>stream +q +/GS0 gs +/Perceptual ri +108 0 0 182 90.3697662 23 cm +/Im0 Do +Q + endstream endobj 279 0 obj <> endobj 282 0 obj <>stream +HA1C/e&!F < zfHg<)YQJTFVTA1eRX˦Q@kKiZ{ +f9(E%h(EeA,'ŴRbVEJ*IIY"RUUԉE*gXaiId){HED pR; KBSʠXuDͬ,jX5XDI֝V&uFɺfYDɪM=Lu}2ք¦5REؚcl m(`€%ZTqo*n)ҖU:ɊYgjEʪiIQ%-嵈ZӂPLiP. D1bY(*e)ma208˰X +5ŒGjҗGp`A% endstream endobj 280 0 obj [/Indexed 271 0 R 1 284 0 R] endobj 283 0 obj <>/Filter/FlateDecode/Height 182/Intent/Perceptual/Length 4763/Name/X/Subtype/Image/Type/XObject/Width 108>>stream +Hi<{O fD$"PZ"(uRҩNTuqZUJ$!B}_0Xzmayy\ow_?bS d5Jg~Հ'͍pqqr̟bq83jP AX|AR<8~A1 II 1A~,6ORx;/bQLFYSGP_[CIZB_kN.eTdD7H5 98:nZga,%ć~c 7 *ad'}7Z.Y Fb812j.Oڿ@UJ͉ +J議~ԹK!?ߺd0!8u.\ +}5k(?/ZEd5X;y ˧V) R5ؼRDܳtWBYk n^^%?hB!ͶC99)qa~[u|t|z1+oZ%!zoNn牠ǯs ?d^;n_hI1<"0+1jD,5 F'+,(+|xd8pO˪??` m u5_/e 4WkKsSCLo *jJ <@ q$*s~`\(t1I%-M]L ;:~Hq14YNQ4sԵu~~覟Ǧrh`uK7Rk'wo0Րġ2.M$r_W{^.Ftƅ@KVBtQʮ3[{ڢgn\HVDL VnKԥ842 w7OtmRb@W w`Ft7jP-^>a c'Si4*UR8/u}a UV@T~;FOQz5(a IY92A*Bנ"+ܸ'󬒺v62::L%wjP%)jGGGZ4(xT Wo8s=65"8 +iQz4Is1|qc#иP4({w>pAZ~esw? 86ƅA1ػ1K:jQ[Q4~'!24iP5ySESw?unkdUB +H@pd CѠLW j1A@WAY)q&ؤA= eߏfZrv1ܤXk<{gzS>`(#iuX]Z8AM(3@kru^.#"077 juv1e׻YkK  Ňayy1'P;3?bLWM^ZB\^"BD> 9{ A^;\mPMEY^r2bD~'8oֳ?BNw23]ldh0Uz:Z2~ |i/=`TGYRϲY N<7 bcb**ׂ݈`e)'җg'8>0 $}fzJIIz0&Q7JG8: ʲ⢂O镗wd$E~ƀ^EyIqY>bEp1 68@&tuvѫKTۚhHÿsLZH*3)12O&1PQp"К*3q1 Qj@?퓈 P_G`q29 8hTJ_a~=*A,XЛ!uԗ$E_m\K^iXHgM(-Uo>rt:`q BZ򾎺Ĩ=W(!2c%Ad-Vw׏:B8XsÆVS$ ˴lű:P[M@Yskkdhs%x;,є&cuNR (wO{3Q'@$pΉ j,M~cjCU)չj~B9vչW+=hV$%8X~o2.Ղ VE߃v_H^i){E@'Vjm^ e?b[swՂCvѐcw0-`NJz_N{gjk|>gBnmjغ^X +i%qf>5vA[3=ArYkZ0B˱P_&^M]kW !Q)m QR^RPk>)UbVhJ-8#(5VL 0!]=$z?[NLls,YK(GdW8eGĨ;{d@ jY [2,wІJ::BD\O5 A`gkI1[ZK7g "_"h5b'wHYѳAQM1XgD|Z < {wGeu MV(nnK:{U\Ndܳ1Pc].Ҳ +ņ^A$nX)z?}u֒X6%n9=01wf?{!£Wpz+JKTl{kv< up,r /R#0ick4(K\pJa8a J8 < +'B7 c PkA 'J7}pNرTɅZt`+pX+zk0kY:B8a,\\}}ʁ7'%74xoϐUVV'XȷƕZpdƭRd!X+xt(BY$şlM|R(7(Far4kɚͰM-U~$kbp??,rI:ZE9x. zܝ<"}M1:"/v"7Bj2o+BjYųVlF&IG;A/g*C?74Kۚp8 3|V>%;UUu!ޗ H$\@*NK'gn>*6W}9X򗯛!>"?^ZX[ +SBD) B +ᄱr _@'E+q=0Q_ jyi EJ/Tsz8a,\?((,g]NKo?*,+/yKN .4 #]-,y,pYtc+ڨԬˉ E X5£.g>|/07' ɽ.a^.V nN,t |_⥌{雓?_ϸ{35P?p"E1ꕋI#,7,1'N= g@ '9 Ckl=AXں ztӎ#ؗ1d0fv3}6|Up=i-KDa7=kAAK9M (L0ۻ\/^9Z"bAMY9L<~Ge0-2d4|!}dP`LИe>̄O!z:d +a422b0T2z2BtK1 +!ȐFFkiicH#hQ?_ # endstream endobj 271 0 obj [/ICCBased 285 0 R] endobj 284 0 obj <>stream + endstream endobj 285 0 obj <>stream +HyTSwoɞc [5, BHBK!aPVX=u:XKèZ\;v^N߽~w.MhaUZ>31[_& (DԬlK/jq'VOV?:OsRUzdWRab5? Ζ&VϳS7Trc3MQ]Ymc:B :Ŀ9ᝩ*UUZ<"2V[4ZLOMa?\⎽"?.KH6|zӷJ.Hǟy~Nϳ}Vdfc +n~Y&+`;A4I d|(@zPZ@;=`=v0v <\$ x +^AD W P$@P>T !-dZP C; t +@A/a<v}a1'X Mp'G}a|OY 48"BDH4)EH+ҍ "~rL"(*DQ)*E]a4zBgE#jB=0HIpp0MxJ$D1(%ˉ^Vq%],D"y"Hi$9@"m!#}FL&='dr%w{ȟ/_QXWJ%4R(cci+**FPvu? 6 Fs2hriStݓ.ҍu_џ0 7F4a`cfb|xn51)F]6{̤0]1̥& "rcIXrV+kuu5E4v}}Cq9JN')].uJ + + wG x2^9{oƜchk`>b$eJ~ :Eb~,m,-Uݖ,Y¬*6X[ݱF=3뭷Y~dó Qti zf6~`{v.Ng#{}}c1X%6fmFN9NN8SΥ'g\\R]Z\t]\7u}&ps[6v_`) {Q5W=b +_zžAe#``/VKPo !]#N}R|:|}n=/ȯo#JuW_ `$ 6+P-AܠԠUA' %8佐b8]+<q苰0C +_ XZ0nSPEUJ#JK#ʢi$aͷ**>2@ꨖОnu&kj6;k%G PApѳqM㽦5͊---SbhZKZO9uM/O\^W8i׹ĕ{̺]7Vھ]Y=&`͖5_ Ыbhו ۶^ Mw7n<< t|hӹ훩' ZL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! +zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Km  endstream endobj 281 0 obj <> endobj 286 0 obj <> endobj 287 0 obj [0.0 0.0 0.0] endobj 288 0 obj <>/ProcSet[/PDF/ImageB]/XObject<>>>/Subtype/Form>>stream +q +/GS0 gs +/Perceptual ri +108 0 0 182 90.3697662 23 cm +/Im0 Do +Q + endstream endobj 289 0 obj <> endobj 291 0 obj <>/Filter/FlateDecode/Height 182/Intent/Perceptual/Length 4763/Name/X/Subtype/Image/Type/XObject/Width 108>>stream +Hi<{O fD$"PZ"(uRҩNTuqZUJ$!B}_0Xzmayy\ow_?bS d5Jg~Հ'͍pqqr̟bq83jP AX|AR<8~A1 II 1A~,6ORx;/bQLFYSGP_[CIZB_kN.eTdD7H5 98:nZga,%ć~c 7 *ad'}7Z.Y Fb812j.Oڿ@UJ͉ +J議~ԹK!?ߺd0!8u.\ +}5k(?/ZEd5X;y ˧V) R5ؼRDܳtWBYk n^^%?hB!ͶC99)qa~[u|t|z1+oZ%!zoNn牠ǯs ?d^;n_hI1<"0+1jD,5 F'+,(+|xd8pO˪??` m u5_/e 4WkKsSCLo *jJ <@ q$*s~`\(t1I%-M]L ;:~Hq14YNQ4sԵu~~覟Ǧrh`uK7Rk'wo0Րġ2.M$r_W{^.Ftƅ@KVBtQʮ3[{ڢgn\HVDL VnKԥ842 w7OtmRb@W w`Ft7jP-^>a c'Si4*UR8/u}a UV@T~;FOQz5(a IY92A*Bנ"+ܸ'󬒺v62::L%wjP%)jGGGZ4(xT Wo8s=65"8 +iQz4Is1|qc#иP4({w>pAZ~esw? 86ƅA1ػ1K:jQ[Q4~'!24iP5ySESw?unkdUB +H@pd CѠLW j1A@WAY)q&ؤA= eߏfZrv1ܤXk<{gzS>`(#iuX]Z8AM(3@kru^.#"077 juv1e׻YkK  Ňayy1'P;3?bLWM^ZB\^"BD> 9{ A^;\mPMEY^r2bD~'8oֳ?BNw23]ldh0Uz:Z2~ |i/=`TGYRϲY N<7 bcb**ׂ݈`e)'җg'8>0 $}fzJIIz0&Q7JG8: ʲ⢂O镗wd$E~ƀ^EyIqY>bEp1 68@&tuvѫKTۚhHÿsLZH*3)12O&1PQp"К*3q1 Qj@?퓈 P_G`q29 8hTJ_a~=*A,XЛ!uԗ$E_m\K^iXHgM(-Uo>rt:`q BZ򾎺Ĩ=W(!2c%Ad-Vw׏:B8XsÆVS$ ˴lű:P[M@Yskkdhs%x;,є&cuNR (wO{3Q'@$pΉ j,M~cjCU)չj~B9vչW+=hV$%8X~o2.Ղ VE߃v_H^i){E@'Vjm^ e?b[swՂCvѐcw0-`NJz_N{gjk|>gBnmjغ^X +i%qf>5vA[3=ArYkZ0B˱P_&^M]kW !Q)m QR^RPk>)UbVhJ-8#(5VL 0!]=$z?[NLls,YK(GdW8eGĨ;{d@ jY [2,wІJ::BD\O5 A`gkI1[ZK7g "_"h5b'wHYѳAQM1XgD|Z < {wGeu MV(nnK:{U\Ndܳ1Pc].Ҳ +ņ^A$nX)z?}u֒X6%n9=01wf?{!£Wpz+JKTl{kv< up,r /R#0ick4(K\pJa8a J8 < +'B7 c PkA 'J7}pNرTɅZt`+pX+zk0kY:B8a,\\}}ʁ7'%74xoϐUVV'XȷƕZpdƭRd!X+xt(BY$şlM|R(7(Far4kɚͰM-U~$kbp??,rI:ZE9x. zܝ<"}M1:"/v"7Bj2o+BjYųVlF&IG;A/g*C?74Kۚp8 3|V>%;UUu!ޗ H$\@*NK'gn>*6W}9X򗯛!>"?^ZX[ +SBD) B +ᄱr _@'E+q=0Q_ jyi EJ/Tsz8a,\?((,g]NKo?*,+/yKN .4 #]-,y,pYtc+ڨԬˉ E X5£.g>|/07' ɽ.a^.V nN,t |_⥌{雓?_ϸ{35P?p"E1ꕋI#,7,1'N= g@ '9 Ckl=AXں ztӎ#ؗ1d0fv3}6|Up=i-KDa7=kAAK9M (L0ۻ\/^9Z"bAMY9L<~Ge0-2d4|!}dP`LИe>̄O!z:d +a422b0T2z2BtK1 +!ȐFFkiicH#hQ?_ # endstream endobj 290 0 obj <> endobj 265 0 obj <> endobj 266 0 obj <> endobj 294 0 obj [/View/Design] endobj 295 0 obj <>>> endobj 292 0 obj [/View/Design] endobj 293 0 obj <>>> endobj 272 0 obj <> endobj 273 0 obj <> endobj 270 0 obj <> endobj 296 0 obj <> endobj 297 0 obj <>stream +%!PS-Adobe-3.0 %%Creator: Adobe Illustrator(R) 17.0 %%AI8_CreatorVersion: 22.0.1 %%For: (Peter Zhang) () %%Title: (logo.ai) %%CreationDate: 2018/3/2 上午1:30 %%Canvassize: 16383 %%BoundingBox: 200 279 409 510 %%HiResBoundingBox: 200.318618775617 279.54498861826 408.824412096368 509.835938915649 %%DocumentProcessColors: Cyan Magenta Yellow Black %AI5_FileFormat 13.0 %AI12_BuildNumber: 249 %AI3_ColorUsage: Color %AI7_ImageSettings: 0 %%RGBProcessColor: 0 0 0 ([套版色]) %AI3_Cropmarks: 178 268 434 524 %AI3_TemplateBox: 305.5 396.5 305.5 396.5 %AI3_TileBox: 26.5 16 585.5 799 %AI3_DocumentPreview: None %AI5_ArtSize: 14400 14400 %AI5_RulerUnits: 2 %AI9_ColorModel: 1 %AI5_ArtFlags: 0 0 0 1 0 0 1 0 0 %AI5_TargetResolution: 800 %AI5_NumLayers: 2 %AI17_Begin_Content_if_version_gt:17 1 %AI9_OpenToView: -385 885 1 1908 1026 18 0 0 1446 -137 0 0 0 1 1 0 1 1 0 1 %AI17_Alternate_Content %AI9_OpenToView: -385 885 1 1908 1026 18 0 0 1446 -137 0 0 0 1 1 0 1 1 0 1 %AI17_End_Versioned_Content %AI5_OpenViewLayers: 77 %%PageOrigin:0 0 %AI7_GridSettings: 72 8 72 8 1 0 0.800000011920929 0.800000011920929 0.800000011920929 0.899999976158142 0.899999976158142 0.899999976158142 %AI9_Flatten: 1 %AI12_CMSettings: 00.MS %%EndComments endstream endobj 298 0 obj <>stream +%%BoundingBox: 200 279 409 510 %%HiResBoundingBox: 200.318618775617 279.54498861826 408.824412096368 509.835938915649 %AI7_Thumbnail: 116 128 8 %%BeginData: 23802 Hex Bytes %0000330000660000990000CC0033000033330033660033990033CC0033FF %0066000066330066660066990066CC0066FF009900009933009966009999 %0099CC0099FF00CC0000CC3300CC6600CC9900CCCC00CCFF00FF3300FF66 %00FF9900FFCC3300003300333300663300993300CC3300FF333300333333 %3333663333993333CC3333FF3366003366333366663366993366CC3366FF %3399003399333399663399993399CC3399FF33CC0033CC3333CC6633CC99 %33CCCC33CCFF33FF0033FF3333FF6633FF9933FFCC33FFFF660000660033 %6600666600996600CC6600FF6633006633336633666633996633CC6633FF %6666006666336666666666996666CC6666FF669900669933669966669999 %6699CC6699FF66CC0066CC3366CC6666CC9966CCCC66CCFF66FF0066FF33 %66FF6666FF9966FFCC66FFFF9900009900339900669900999900CC9900FF %9933009933339933669933999933CC9933FF996600996633996666996699 %9966CC9966FF9999009999339999669999999999CC9999FF99CC0099CC33 %99CC6699CC9999CCCC99CCFF99FF0099FF3399FF6699FF9999FFCC99FFFF %CC0000CC0033CC0066CC0099CC00CCCC00FFCC3300CC3333CC3366CC3399 %CC33CCCC33FFCC6600CC6633CC6666CC6699CC66CCCC66FFCC9900CC9933 %CC9966CC9999CC99CCCC99FFCCCC00CCCC33CCCC66CCCC99CCCCCCCCCCFF %CCFF00CCFF33CCFF66CCFF99CCFFCCCCFFFFFF0033FF0066FF0099FF00CC %FF3300FF3333FF3366FF3399FF33CCFF33FFFF6600FF6633FF6666FF6699 %FF66CCFF66FFFF9900FF9933FF9966FF9999FF99CCFF99FFFFCC00FFCC33 %FFCC66FFCC99FFCCCCFFCCFFFFFF33FFFF66FFFF99FFFFCC110000001100 %000011111111220000002200000022222222440000004400000044444444 %550000005500000055555555770000007700000077777777880000008800 %000088888888AA000000AA000000AAAAAAAABB000000BB000000BBBBBBBB %DD000000DD000000DDDDDDDDEE000000EE000000EEEEEEEE0000000000FF %00FF0000FFFFFF0000FF00FFFFFF00FFFFFF %524C45FD05FFA9857E855A855A857EA9A8FD66FF847EFD055A845A7E5A7E %5A5A5A8584FD63FF855A845A855A845A855A845A855A845A845AA9FD61FF %7EFD045A7E5A5A5A7E5A5A5A7E5A5A5A7E5A7E84FD19FFA8A984A97E845A %845A7E5A845A845A847E8584A9A8FD31FFAF5A855A855A855A855A855A85 %5A855A855A855A8584FD13FFAFAF84855A855A855A845A855A855A855A85 %5A855A845A855A855A8584AFAFFD2CFF845A5A7E5A845A7E5A845A7E5A84 %5A7E5A845A7E5A5A5AAFFD0EFFA8855A7E5A5A5A7E5A7E5A845A7E5A845A %7E5A845A7E5A845A7E5A84FD055A7E5AA9A9FD29FFAF5A855A845A855A84 %5A855A845A855A845A855A845A855AAFFD0AFFA9855A855A7E5A855A845A %855A845A855A845A855A845A855A845A855A845A855A845A855A7E5A857E %A9AFFD26FFA85A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A %A9FD06FFA884FD055A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E %5A5A5A7E5A5A5A7E5A5A5A7EFD055A7E7EFD26FF5A855A855A855A855A85 %5A855A855A855A855A855A855A855AAFFFFFFFAF7E845A855A855A855A85 %5A855A855A855A855A855A855A855A855A855A855A855A855A855A855A85 %5A855A855A855A855A85A9FD23FF7E5A845A7E5A845A7E5A845A7E5A845A %7E5A845A7E5A845A5A5AAFA87E5A7E5A7E5A845A7E5A845A7E5A845A7E5A %845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A %7E5A7E5A5A7EAFFD21FF847E5A855A845A855A845A855A845A855A845A85 %5A845A855A845A855A845A855A845A855A845A855A845A855A845A855A84 %5A855A845A855A845A855A845A855A845A855A845A855A845A855A845A85 %5A7E5A85AFFD1FFF85FD0B5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A %5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A %7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E84FD1EFF84 %7E7EA9A9AFA9FFA9A984855A7E5A855A855A855A855A855A855A855A855A %855A855A855A855A855A855A855A855A855A855A855A855A855A855A855A %855A855A855A855A855A855A855A855A855A855A855A855A7E7EFD1EFFA9 %FD0BFFA8845A5A5A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A %845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A %7E5A845A7E5A845A7E5A845A7E5A845A5A5AAFFD2BFF84855A845A855A84 %5A855A845A855A845A855A845A855A845A855A845A855A845A855A845A85 %5A845A855A845A855A845A855A845A855A845A855A845A855A845A855A84 %5A855A7E5AA9FD2BFFA8855A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A %7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A %5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A85FD2BFF7E855A85 %5A855A855A855A855A855A855A855A855A855A855A855A855A855A855A85 %5A855A855A855A855A855A855A855A855A855A855A855A855A855A855A85 %5A855A855A855A855AA9FD28FFA95A7E5A7E5A845A7E5A845A7E5A845A7E %5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A84 %5A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A7E5AA9 %FD26FFA95A845A855A845A855A845A855A845A855A845A855A845A855A84 %5A855A845A855A845A855A845A855A845A855A845A855A845A855A845A85 %5A845A855A845A855A845A855A845A855A845A855AAFFD24FF845A7E5A5A %5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E %5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A %5A7E5A5A5A7E5A5A5A7E5A5A5A7E5AFD23FF855A855A855A855A855A855A %855A855A855A855A855A855A855A855A855A855A855A855A855A855A855A %855A855A855A855A855A855A855A855A855A855A855A855A855A855A855A %855A855A855A855A7E7EFD21FF855A7E5A845A7E5A845A7E5A845A7E5A84 %5A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E %5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A84 %5A7E5A845A5A84FD1FFF855A855A845A855A845A855A845A855A845A855A %845A855A845A855A845A855A845A855A845A855A845A855A845A855A845A %855A845A855A845A855A845A855A845A855A845A855A845A855A845A855A %845A855A7EA9FD1DFF855A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E %5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A %5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E %5A5A5A7E5A7EA8FD1BFFA95A855A855A855A855A855A855A855A855A855A %855A855A855A855A855A855A855A855A855A855A855A855A855A855A855A %855A855A855A855A855A855A845A855A845A855A845A855A855A855A855A %855A855A855A855AA9FD1AFFA95A7E5A7E5A845A7E5A845A7E5A845A7E5A %845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A %7E5A845A7E5A845A7E5A845A7E5A7E5A5A535A595A5A7E5A7E5A845A7E5A %845A7E5A845A7E5A845A5A7EFD1AFF5A7E5A855A845A855A845A855A845A %855A845A855A845A855A845A855A845A855A845A855A845A855A845A855A %845A855A845A855A845A855A845A855A7E5A5A5AA97E5A595A5A845A845A %855A845A855A845A855A845A85A9FD1AFFFD055A7E5A5A5A7E5A5A5A7E5A %5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A %7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A59FFFFFF8459535A597E5A %5A5A7E5A5A5A7E5A5A5A7E5A84A8FD1AFF84855A855A855A855A855A855A %855A855A855A855A855A855A855A855A855A855A855A855A855A855A855A %855A855A855A855A855A855A855A855A855A855A7E59FD05FF5959597E5A %845A855A855A855A855A855AAFFD1BFFA95A5A845A7E5A845A7E5A845A7E %5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A84 %5A7E5A845A7E5A845A7E5A845A7E5A845A7E5A7E5384FFFFCAFFA8592F59 %597E5A7E5A845A7E5A845A5A5AFD1DFF855A845A855A845A855A845A855A %845A855A845A855A845A855A845A855A845A855A845A855A845A855A845A %855A845A855A845A855A845A855A845A855A7E5A7EFFFFA8CAFFA92E5953 %5A5A855A845A855A845A8584FD1DFFA95A7E5A5A5A7E5A5A5A7E5A5A5A7E %5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A %5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E595AA8FFFFA0A8FF7D2E2E59 %595A5A7E5A5A5A7E5A7EA8FD05FFA8FD18FF5A855A855A855A855A855A85 %5A855A855A855A855A855A855A855A855A855A855A855A855A855A855A85 %5A855A855A855A855A855A855A855A855A855A855A5A7EFFFFC399FFFF59 %2E59597E5A855A855A855AA9FD05FFA852A8FD17FF845A5A845A7E5A845A %7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A %845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A84FD045A59FFFFCF6EA1 %FFFF282F2F5A595A5A7E5A5A5AAFFD04FFA87D527D52A8FD15FFAF855A84 %5A855A845A855A845A855A845A855A845A855A845A855A845A855A845A85 %5A845A855A845A855A845A855A845A855A845A855A845A855A845A855A7E %53AFFFFF9999CAFF842E2F59537E5A845A7E84FD05FFA87D527D527D7DFD %15FFA95A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A %5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A %7EFD045A537EFFFFA1926FFFFF7D0553535A597E5A5A84FD05FF7D52527D %527D527D7DFD14FF5A855A855A855A855A855A855A855A855A855A855A85 %5A855A855A855A855A855A855A855A855A855A855A855A855A855A855A85 %5A855A855A855A845A845A7EFFFFCA996EC2FFFF5253535A5A845A85A9FD %05FFFD0A7DA8FD12FFA85A5A845A7E5A845A7E5A845A7E5A845A7E5A845A %7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A %845A7E5A845A7E5A7E5A5A595AA8FFFFA06E92A1FFA82E2E59595A5AA9FD %05FF7D527D527D527D527D527D527DA8FD11FF855A845A855A845A855A84 %5A855A845A855A845A855A845A855A845A855A845A855A845A855A845A85 %5A845A855A845A855A845A855A845A855A845A845A5A7EFFFFC96E9999CF %FFA82853535A5AAFFD04FFA87D527D527D7D7D527D7D7D527D527DFD10FF %AF5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A %7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A %5A595A59FFFFFF6E986E99FFFF522E2E5959FD05FF7D7D527D527D527D52 %7D527D527D527D52A8FD0FFF84855A855A855A855A855A855A855A855A85 %5A855A855A855A855A855A855A855A855A855A855A855A855A855A855A85 %5A855A855A855A855A855A845A7E59A9FFFFA0997499A0FFFF532E597EFD %05FF7D7D52FD107DFD0FFF845A845A7E5A845A7E5A845A7E5A845A7E5A84 %5A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E %5A845A7E5A845A7E5A7E5A5A2F84FFFFA1986E996ECAFFA805537DCFA8FF %FFA8527D527D527D527D527D527D527D527D527D527D7DFD0EFF5A845A85 %5A845A855A845A855A845A855A845A855A845A855A845A855A845A855A84 %5A855A845A855A845A855A845A855A845A855A845A855A845A845A5AA9FF %CA996E999299CAFF7D527DA8A8FFA87D527D527D7D7D527D7D7D527D7D7D %527D7D7D527D7D7DA8FD0CFF84FD045A7E5A5A5A7E5A5A5A7E5A5A5A7E5A %5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A %7E5A5A5A7E5A5A5A7E5A5A595A84FFFFA06E996E9999FFFF7D527DA8FF7D %7D527D527D527D527D527D527D527D527D527D527D527D52A8FD0CFFA95A %855A855A855A855A855A855A855A855A855A855A855A855A855A855A855A %855A855A855A855A855A855A855A855A855A855A855A855A855A845A5A7E %FFFFCA6E996E996EC9FFFF527D7DA87D7D52FD177D52FD0CFF5A7E5A7E5A %845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A %7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A7E5A5A53FFFFFF6E %996E996E99A7FFA87D7D7D527D527D527D527D527D527D527D527D527D52 %7D527D527D527D527D7DFD0AFFA97E5A855A845A855A845A855A845A855A %845A855A845A855A845A855A845A855A845A855A845A855A845A855A845A %855A845A855A845A855A845A7E59A9FFFFA0996E99999975FFFFA8527D52 %7D527D7D7D527D7D7D527D7D7D527D7D7D527D7D7D527D7D7D527D7D7DA8 %FD09FFA95A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A %5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E %5A5A2F7EFFFFA1986E996E996EA0FFFF52272752527D527D527D527D527D %527D527D527D527D527D527D527D527D527D52A8FD09FF847E5A855A855A %855A855A855A855A855A855A855A855A855A855A855A855A855A855A855A %855A855A855A855A855A855A855A855A855A855A845A7EAFFFCF9992996E %999999A1FFA8522752527D52FD1C7DFD09FF855A7E5A845A7E5A845A7E5A %845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A %7E5A845A7E5A845A7E5A845A7E5A7E5A5A84FFFFA06E996E996E996ECAFF %A82752527D527D527D527D527D527D527D527D527D527D527D527D527D52 %7D527D527D7DFD08FF5A855A845A855A845A855A845A855A845A855A845A %855A845A855A845A855A845A855A845A855A845A855A845A855A845A855A %845A855A7E5A5A7EFFFFCA6E996E9999996E9FFFFFFD05527D7D7D527D7D %7D527D7D7D527D7D7D527D7D7D527D7D7D527D7D7D527D7D7DFD07FFA85A %5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E %5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A53FFFFFF %6E996E996E996E929AFFFF5227FD04527D527D527D527D527D527D527D52 %7D527D527D527D527D527D527D527D52A8FD06FFAF5A855A855A855A855A %855A855A855A855A855A855A855A855A855A855A855A855A855A855A855A %855A855A855A855A855A855A855A855A7E53A9FFFFA09999996E9999996E %CAFFFF2752527D52FD207DFD06FF845A5A845A7E5A845A7E5A845A7E5A84 %5A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E %5A845A7E5A845A7E5A7E597EFFFFA7986E996E996E996E99A8FF7D272752 %527D527D527D527D527D527D527D527D527D527D527D527D527D527D527D %527D527DA8FD05FFA95A845A855A845A855A845A855A845A855A845A855A %845A855A845A855A845A855A845A855A845A855A845A855A845A855A845A %855A7E5A5AA9FFFF996E996E9999996E9999FFFF7D2752527D527D527D7D %7D527D7D7D527D7D7D527D7D7D527D7D7D527D7D7D527D7D7D527D52A8FD %05FF7EFD045A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A %7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E595A7EFFFF %A06E996E996E996E996EA1FFFF27FD04527D527D527D527D527D527D527D %527D527D527D527D527D527D527D527D527D527D7DFD05FF855A855A855A %855A855A855A855A855A855A855A855A855A855A855A855A855A855A855A %855A855A855A855A855A855A855A855A855A5A5AFFFFCA6E9999996E9999 %996E99CAFFA8272752527D527D527D527D527D527D527D7D7D52FD137DA8 %FD04FF5A7E5A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A %7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A7E5A7E5A5A53AFFF %FF75986E996E996E996E996EFFFF7DF8272752275252522752525227FD04 %527D527D527D527D527D527D527D527D527D527D527D52A8FD04FF845A85 %5A845A855A845A855A845A855A845A855A845A855A845A855A845A855A84 %5A855A845A855A845A855A845A855A845A855A5A5AFFFFFFA09999996E99 %99996E996EA0FFFFA8A87DFD05A87DA87DA87DA87DA87D7D527D527D527D %7D7D527D7D7D527D7D7D527D7D7D527D7DFD04FF5A5A5A7E5A5A5A7E5A5A %5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E %5A5A5A7E5A5A5A7E5A5A5A7E7EA8FFFFA7986E996E996E996E996E98A1FD %14FF7D5252527D527D527D527D527D527D527D527D527D527DA8FFFFFF85 %5A855A855A855A855A855A855A855A855A855A855A855A855A855A855A85 %5A855A855A855A855A855A855A855A855A845A85A8FD04FF996E9999996E %9999996E9998C3A7CAA7CAA8CFCACAA8CFCACFCAFFCAFFCAFFFFFFA85252 %7D52FD117D52A8FFFFFF5A7E5A845A7E5A845A7E5A845A7E5A845A7E5A84 %5A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A7E5A84 %A8FFA8FFFFA16E996E996E996E996E996E996E926E996E996E996E996E99 %6E996E996E9899FFFFA82752527D527D527D527D527D527D527D527D527D %527D7DFFFFFF855A845A855A845A855A845A855A845A855A845A855A845A %855A845A855A845A855A845A855A845A855A845A855A5A5AFFFFFFA8FFFF %CA6E9999996E9999996E9999996E9999996E9998996E9998996E9998996E %996ECFFFFF5252527D527D527D7D7D527D7D7D527D7D7D527D7D7D527DFF %FFFF5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A %5A7E5A5A5A7E5A5A5A7E5A5A5A7EFD045A7EFFCFFFA8FFFFFF75926E996E %996E996E996E996E996E996E996E996E996E996E996E996E996EA1FFFF52 %272752527D527D527D527D527D527D527D527D527D527D52A8FFFF855A85 %5A855A855A855A855A855A855A855A855A855A855A855A855A855A855A85 %5A855A855A855A855A855A85A9FFFFFFA8A8FFFFA1996E9999996E999999 %6E9999996E9999996E9999996E9999996E9999996EA0FFFFA8522752527D %527D52FD117DA8FFFF5A7E5A7E5A845A7E5A845A7E5A845A7E5A845A7E5A %845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A7E5A84A8FFFFFF7D7DA8 %FFA8996E996E996E996E996E996E996E996E996E996E996E996E996E996E %996E98A7FFFF5227FD04527D527D527D527D527D527D527D527D527D527D %527DFFFF855A855A845A855A845A855A845A855A845A855A845A855A845A %855A845A855A845A855A845A855A7E5AAFFD04FF7D52A8FFFF9A6E999999 %6E9999996E9999996E9999996E9999996E9999996E9999996E99A0FFFF7D %2752527D527D527D7D7D527D7D7D527D7D7D527D7D7D52FD047DFFFF845A %5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E %5A5A5A7EFD055AAFFFFFFFA852527DFFFFA16E996E996E996E996E996E99 %6E996E996E996E996E996E996E996E996E986EFFFFA827522752527D527D %527D527D527D527D527D527D527D527D527D527DA8FFAF5A855A855A855A %855A855A855A855A855A855A855A855A855A855A855A855A855A855A855A %8584FD04FFA8527D7DFFFFFF98996E9999996E9999996E9999996E999999 %6E9999996E9999996E9999996ECAFFFF52275252527D527D52FD137D52A8 %FFA85A5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A %7E5A845A7E5A845A7EA8FFFFFFA87D525252A8FFFF75996E996E996E996E %996E996E996E996E996E996E996E996E996E996E996EA0FFFF7D2727FD04 %527D527D527D527D527D527D527D527D527D527D527D527D7DFFFF5A845A %855A845A855A845A855A845A855A845A855A845A855A845A855A845A855A %7E5AA9FD04FFA87D527D52A8FFFFA1986E9999996E9999996E9999996E99 %99996E9999996E9999996E9999996E99CAFFA8522752527D527D527D7D7D %527D7D7D527D7D7D527D7D7D527D7D7D527D527DFFFF845A5A5A7E5A5A5A %7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7EFD055AA9FD04FF7D7D %527D527DA8FFA8996E996E996E996E996E996E996E996E996E996E996E99 %6E996E996E996E92A0FFFF5227FD04527D527D527D527D527D527D527D52 %7D527D527D527D527D527D7DFFFF847E5A855A855A855A855A855A855A85 %5A855A855A855A855A855A855A855A8584FD04FFA8FD047D527DA8FFFFA0 %6E996E9999996E9999996E9999996E9999996E9999996E9999996EFD0499 %FFFFA827FD04527D7D7D52FD177DFFFFAF5A7E5A845A7E5A845A7E5A845A %7E5A845A7E5A845A7E5A845A7E5A845A5A84FFFFFFA8A8527D527D52527D %FFFFA76E996E996E996E996E996E996E996E996E996E996E996E996E996E %996E996ECAFFFF272727FD04527D527D527D527D527D527D527D527D527D %527D527D527D527D52A8FFAF855A845A855A845A855A845A855A845A855A %845A855A845A855A845A85A9FFFFFFA87D527D7D7D527D52FFFFFF92996E %9992996E9992996E9992996E9992996E9999996E9999996E9999996EC3FF %FF52272752527D527D527D7D7D527D7D7D527D7D7D527D7D7D527D7D7D52 %7D7D7D527DA8FFFF7EFD045A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7EFD %055A85A8FFFFFF7D7D527D527D527D52A8FFFF99996E996E996E996E996E %996E996E996E996E996E996E996E996E996E996E99A8FFA82727FD04527D %527D527D527D527D527D527D527D527D527D527D527D527D527D52A8FFFF %AF5A855A855A855A855A855A855A855A855A855A855A855A857EAFFD04FF %FD087D527DA8FD14FFC36E9999996E9999996E999999A7FFFF522752527D %52FD1D7DA8FFFFA9845A7E5A845A7E5A845A7E5A845A7E5A845A7E5A845A %5A7EFD04FFA8527D527D527D527D527D527DFD14FF99986E996E996E996E %996E989AFFFF7D27522752527D527D527D527D527D527D527D527D527D52 %7D527D527D527D527D527D52A8FFFFFF84845A845A855A845A855A845A85 %5A845A855A845A85A9FD04FF7D527D7D7D527D7D7D527D7D7D52FD077D52 %7D7D7D527D7D7D527DA8FFCA9998996E9999996E99999975FFFFFF27FD04 %527D527D527D7D7D527D7D7D527D7D7D527D7D7D527D7D7D527D7D7D52FD %047DFFFFFFAF5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A5A5A7E5A84A8FFFFFF %7D7D527D527D527D527D527D527DFD0452FD0E27F8A8FFC96E996E996E99 %6E996E996EA7FFFF522727FD04527D527D527D527D527D527D527D527D52 %7D527D527D527D527D527D527D527DFD04FFA95A855A855A855A855A855A %855A855A855AA9FD04FFFD107D527DFD0E522752FFFFA09999996E999999 %6E996EA0FFFF7D272752527D52FD207DFD04FFA85A5A7E5A845A7E5A845A %7E5A845A5A5AAFFFFFFFA8527D527D527D527D527D527D527D527D527D52 %7DFD0F52FFFF996E996E996E996E996E99A8FFA85227FD04527D527D527D %527D527D527D527D527D527D527D527D527D527D527D527D527D527DFD05 %FF7E855A845A855A845A855A845A8484FD04FFA8527D7D7D527D7D7D527D %7D7D527D7D7D527D7D7D527D527D527D527D527D527D527D52A8FFFF9999 %6E9999996E999998A0FFFF7D27FD04527D7D7D527D7D7D527D7D7D527D7D %7D527D7D7D527D7D7D527D7D7D527D7D7D527D7DFD05FFAF5A7E5A5A5A7E %5A5A5A7E5A7E84FFFFFFA87D527D527D527D527D527D527D527D527D527D %527D527D527D527D527D527D527DFD0452A8FFA1986E996E996E996E9875 %FFFFA8272727FD04527D527D527D527D527D527D527D527D527D527D527D %527D527D527D527D527D527DFD06FFA95A855A855A855A855A85AFFFFFFF %FD187D527D7D7D527D7D7D527D7D7D527D52FFFFC36E996E9999996E9992 %CFFFFF52272752527D52FD227DFD06FFA9845A7E5A845A5A5AA9FD04FF7D %7D527D527D527D527D527D527D527D527D527D527D527D527D527D527D52 %7D527D527D527D527DFFFF75996E996E996E996EA1FFFF52272752527D52 %7D527D527D527D527D527D527D527D527D527D527D527D527D527D527D52 %7D527D527DFD07FFA8845A845A847EFD04FFA8527D7D7D527D7D7D527D7D %7D527D7D7D527D7D7D527D7D7D527D7D7D527D7D7D527D7D7D527D5252A8 %FFCA996E9999996E996E99CFFFA8272752527D527D7D7D527D7D7D527D7D %7D527D7D7D527D7D7D527D7D7D527D7D7D527D7D7D52FD047DFD08FF7E5A %5A5A7EFFFFFFA87D527D527D527D527D527D527D527D527D527D527D527D %527D527D527D527D527D527D527D527D527D52A8FFC96E996E996E996E98 %A7FFFF5227522752527D527D527D527D527D527D527D527D527D527D527D %527D527D527D527D527D527D527D527DFD09FF5A85A9FD04FFA852FD257D %527D527DFFFFA0996E9999996E99A0FFFFA827FD04527D52FD247DFD0AFF %A9FD06FF7D527D527D527D527D527D527D527D527D527D527D527D527D52 %7D527D527D527D527D527D527D52527DFFFF996E996E996E996EFFFFFF27 %FD04527D527D527D527D527D527D527D527D527D527D527D527D527D527D %527D527D527D527D527D527DFD12FF7D527D527D7D7D527D7D7D527D7D7D %527D7D7D527D7D7D527D7D7D527D7D7D527D7D7D527D7D7D52A8FFCF6E99 %99996E996EC9FFFF52272752527D527D7D7D527D7D7D527D7D7D527D7D7D %527D7D7D527D7D7D527D7D7D527D7D7D527D7D7D527D7DFD12FFA87D527D %527D527D527D527D527D527D527D527D527D527D527D527D527D527D527D %527D527D5252A8FFA1926E996E996EA0FFFF7D272752527D527D527D527D %527D527D527D527D527D527D527D527D527D527D527D527D527D527D527D %527D52A8FD13FFA87D52FD1F7D52FD047DFFFFA06E9999996E99CAFFFF52 %2752527D527D52FD257DA8FD14FF7D7D527D527D527D527D527D527D527D %527D527D527D527D527D527D527D527D527D527D527DFFFF75996E996E92 %A0FFFF7D27FD04527D527D527D527D527D527D527D527D527D527D527D52 %7D527D527D527D527D527D527D527D527D52A8FD15FF7D7D527D7D7D527D %7D7D527D7D7D527D7D7D527D7D7D527D7D7D527D7D7D527D7D7D527DA8FF %A89998996E9999FFFFA827522752527D527D7D7D527D7D7D527D7D7D527D %7D7D527D7D7D527D7D7D527D7D7D527D7D7D527D7D7D527D7D7DA8FD16FF %7D52527D527D527D527D527D527D527D527D527D527D527D527D527D527D %527D525252FFFFA16E996E996ECAFFFF27272752527D527D527D527D527D %527D527D527D527D527D527D527D527D527D527D527D527D527D527D527D %527D52FD18FFFD207D527DFFFFA09999996EC3FFFF7D275252527D527D52 %FD277DFD19FF527D527D527D527D527D527D527D527D527D527D527D527D %527D527D527D52527DFFFF996E996E99CAFFA82727FD04527D527D527D52 %7D527D527D527D527D527D527D527D527D527D527D527D527D527D527D52 %7D527D527D7DFD1AFFFD047D527D7D7D527D7D7D527D7D7D527D7D7D527D %7D7D527D7D7D527D52A8FFCF6E996E99A7FFFF522752527D527D527D7D7D %527D7D7D527D7D7D527D7D7D527D7D7D527D7D7D527D7D7D527D7D7D527D %7D7D527D7D7D52A8FD1BFF527D527D527D527D527D527D527D527D527D52 %7D527D527D527D527D5252A8FFA0926E9299FFFF7DF8522752527D527D52 %7D527D527D527D527D527D527D527D527D527D527D527D527D527D527D52 %7D527D527D527D527DA8FD1CFFFD1A7D527D7DFFFFA06E9974FFFFFF27FD %04527D7D7D52FD277D52FD1EFF7D7D527D527D527D527D527D527D527D52 %7D527D527D527D527D52A8FFFF6E996EA7FFFF52272752527D527D527D52 %7D527D527D527D527D527D527D527D527D527D527D527D527D527D527D52 %7D527D527D527D527DA8FD1EFF7D7D527D7D7D527D7D7D527D7D7D527D7D %7D527D7D7D527D5252A8FFCA996EA0FFFF7D272752527D527D527D7D7D52 %7D7D7D527D7D7D527D7D7D527D7D7D527D7D7D527D7D7D527D7D7D527D7D %7D527D7D7D527D7DFD20FF7D52527D527D527D527D527D527D527D527D52 %7D527D527D52FFFFA16E98A8FFA82727FD04527D527D527D527D527D527D %527D527D527D527D527D527D527D527D527D527D527D527D527D527D527D %527D52A8FD21FFFD147D527D527DFFFF9A92A0FFFF7D27FD04527D7D7D52 %FD277D527DA8FD22FF7D7D527D527D527D527D527D527D527D527D527D52 %527DFFA89975FFFFA827522752527D527D527D527D527D527D527D527D52 %7D527D527D527D527D527D527D527D527D527D527D527D527D527D527D52 %FD24FFA87D527D527D7D7D527D7D7D527D7D7D527D7D7D52FFFFCA6ECAFF %FF27FD04527D527D527D7D7D527D7D7D527D7D7D527D7D7D527D7D7D527D %7D7D527D7D7D527D7D7D527D7D7D527D7D7D527D52A8FD25FFA87D527D52 %7D527D527D527D527D527D527D5252A8FF9AA0FFFF7D2727FD04527D527D %527D527D527D527D527D527D527D527D527D527D527D527D527D527D527D %527D527D527D527D527D527DA8FD27FFA852FD0B7D52FD047DFFFFA0CAFF %A8272752527D52FD2E7DFD29FFA8527D527D527D527D527D527D527D52A8 %FFCFA7FFFF5227FD04527D527D527D527D527D527D527D527D527D527D52 %7D527D527D527D527D527D527D527D527D527D527D527D527D52A8FD2BFF %FD047D527D7D7D527D7D7D527DA8FFCAFFFFA827FD04527D7D7D527D7D7D %527D7D7D527D7D7D527D7D7D527D7D7D527D7D7D527D7D7D527D7D7D527D %7D7D527D7D7D527D7D7DA8FD2CFF7D7D527D527D527D527D527D52FD05FF %27272752527D527D527D527D527D527D527D527D527D527D527D527D527D %527D527D527D527D527D527D527D527D527D527D527D52FD2EFFA87D52FD %077D527D7DFFFFFFFD05527D52FD2D7D52A8FD2FFFA87D527D527D527D52 %7D52527DA8525227FD04527D527D527D527D527D527D527D527D527D527D %527D527D527D527D527D527D527D527D527D527D527D527D527D527DA8FD %31FFA8527D7D7D527D7D7DFD07527D527D7D7D527D7D7D527D7D7D527D7D %7D527D7D7D527D7D7D527D7D7D527D7D7D527D7D7D527D7D7D527D7D7D52 %FD047DFD34FF7D7D527D527DFD07527D527D527D527D527D527D527D527D %527D527D527D527D527D527D527D527D527D527D527D527D527D527D527D %527D527D52A8FD35FFA87D527D7D7D527D527D527D527D52FD2F7DFD38FF %A8527D527D527D527D527D527D527D527D527D527D527D527D527D527D52 %7D527D527D527D527D527D527D527D527D527D527D527D527D527D527DA8 %FD3AFF7D7D527D527D7D7D527D7D7D527D7D7D527D7D7D527D7D7D527D7D %7D527D7D7D527D7D7D527D7D7D527D7D7D527D7D7D527D7D7D52FD047DFD %3CFFA8A85252527D527D527D527D527D527D527D527D527D527D527D527D %527D527D527D527D527D527D527D527D527D527D527D527D527D52A8FD3F %FFA87D527D52FD2F7DFD41FFA8A87D7D527D527D527D527D527D527D527D %527D527D527D527D527D527D527D527D527D527D527D527D527D527D527D %527DA8FD44FFA8A87D7D527D527D7D7D527D7D7D527D7D7D527D7D7D527D %7D7D527D7D7D527D7D7D527D7D7D527D7D7D52FD047DFD48FFA87D527D52 %7D527D527D527D527D527D527D527D527D527D527D527D527D527D527D52 %7D527D527D527D52A8FD4BFFA8A87D7D52FD217D527DFD4FFFA8A87D7D52 %7D527D527D527D527D527D527D527D527D527D527D527D527D527D527D52 %7DA8FD54FFA8A8FD047D527D527D527D7D7D527D7D7D527D7D7D527D527D %527D527D7DFD58FFA8FFA8A87DA87D7D527D527D527D527D527D52FD047D %A87DA8A8FD0CFFFF %%EndData endstream endobj 299 0 obj <>stream +%AI12_CompressedDataxY3Iv?}_2/}wP +Ka=>c)P,?X?p(Ac<=|_pDfLBR>7w{#tZƸeG +.IqGz}: aR>;ݰnT:vĎqBsh5w[)8p9'@.+mu].g5Dn$ܲm.E>Nj vߜ^wݨ x]^҃~'PݦvknzGwVY [ebMV +'(\%D]{<%Nrڌq,'>w.~?F `: b#`H/O94}#VEW:.tS[f'BGYbw緳HI>0TY/@ \gˋ\k _FAk@M| \I=o_Jg@\_~8`dn'!|v^/FG`>^:X[c[P_P'AQ +;_%>?>=@38ix$ t@vh\ 3ԦKt\ɂ k@԰(%|;=0XH$I@Ջ4Ғz~(Pv5S\P'X3p8N0A | +n)ƹ!~Ѐ¢`\wpze'So)XPi;`$n"P1 pPs\qJw]]V~ XQYVr +<7 )CJydXHIKRuC0MCk^ 6`Yn7:x X**| |?> ܮĢA~HP9ny>C h#q)16M@ lȢeA+on-AT.h2ֆ(qPґ+=ɧLlmt{KFL(Z%a^=ךDP;@ $7 #;- <@:g>BdM}˘49cքbo1@~$CmC2ňULR_dgW#dx" +2F> *?͟W~Pf(@1-(-5WM/i.԰5MrωޥMecΔ#7H lB'ngG類#))t#;.Lwc7~~w_v9MC@.=EX#p`7(!/ÅZRq`60 5 MBXoTO(Y m'`+k=ta`{sTEAHifm`Zr|:8A`Tu_?d\O FF)snǪF`!0M֧s[AHoD3^;e $4I?;G 9n§rvnEpeu3CKRB~#{æg.ӛ?aJ4fC.;EFD 忆Uz}XaSŗ $ǻ#d%|vP߯DfД3~Bv\.'Rv9=.̌n{ ;F_rz\7LtGab;L@XK#84S:H!/<J8w1UN`*ҩ_H6%Nw]a+i_/q^8?mÊ\$4qw ȝŜ-< +:#Ntw:sRa3@q2p8c{P xGw`L˩p9sQQ;b~VHdvK8RɤQp@oܕ<`˟o~^ކ31kMK̔m֮A$PpS'ݑker0?i55>9Ymgi͈\Wqy e/ɦ ΔlKLd<2J|+[ώdV6^Gzdso^xG^#8B)~8w;D9zQ= ;_{0U Lus~ao}C&*Np;Fʂi ف4һQmY{p'py +>zG؝R +/ccR "PP) X2h*EU,Z3̸Y]".Px^wWmWcp{Df (n m$V*es!hgO؏DؔS{pr-&’MePlǢD '"MCRb—*$G.gA@*%'lx5mZ6Lj4;i>Ctjxz+ p&ְ2y-dLb/T+gx4XMӛ1 vf5~k.vDXd0Q{Iؑܬn?T.K`%`/OD֋Ӝ|{/!ty7(-X8|' $m6X,-9*M&l^P]7&O; .>K`rW.e-nXP~~lD绺FDS&X?Ǣ`Zx+ ijkIM>1 q ,:wc;97l>;4=sl)nSL5y~ n`$B7rza7 +J]ͻbQ.t%@sP!EL) %77׉`fe;>fvj߇(u|dw~:@W&Wyu86~?&~ODFLXl<^ogؒ2JOË<0 3Sd(q B ِO%4hC-dMM ;F^.SZod[ m#gxmj\c{F[xk~Qycx:^kf| jS3X(p=PC؊_>n}?=Z@KxJKṊB^4ktB΢]=&l{OPИO?˟~/~_,n8+eH5dKґj/<$htiv2`+rOE6JgY'Sor3HR-/=>xKBl3$,Ki s +I55Y5|-ƒ \#ɖ2"M;1l xlL,V 8^77Űq ,,~('f; +/9)4@#KCC[ 2}Ov;z`%B|hFa_9G>^{5-ShY`K<::dp])QyS\]OD,NnlVv" + 1vC|dv;*1DdX$\@tBifl[ s h%8n,`{lKBGuJ 8E'CoާX<ƈHv dzRT[ _pbHWye'-mfc{6%>UI?o?Uٵ[Tf]RWZrSDF=k12\.%RcKF)%'|}Ih10EuC`Qx-7 |o]S +Ȍi( WHM׼I߁gQ%u*a|7K*-c#_c_9-U 炖U9(/l&9d^n敯ר] ĉ;b`xnxv7Y/ڸrτ yb|;{wb<~|{Ƃ#o4҆ETHc7 [n'!4@2w:NT\LI$% X#\.-*3b?g@.gK1nj1j>}/x{}G&Aa(iP]$PB,XdH 8c-w@Ll\fДRɋx,7oi}\.$D]-Gbyqd>pH6nžjKĈfoUj4$si&kg#ew4X14eβ_9Ss+^ؓuNB TĹQᘘؓo&$e7JKetAKz&/6k|+V Kī9eRnm76t )y;3_[I{G(]p{ZeSƘ:Y7#ٰKc,8&7}j>bOQC |'G*?ͫ66޴iI -S7&Q)s-Kڙ$~2G:"4`+iiIJ +280Ծiblep8~S,TX:o3ŷ. +NLeD/=NRCg}]2 . -6+Xjj.?iikst/{RQHcA\jc- P?4K-zZv"w&Xo5S[dhB]Bg.=B-ĢQB~ϒ♭.E%{LDvЅ,1Z ?YdIwP먎 +-*9dLgBk9a_ƹx:5xƨߛ o%=F) ^ .qAF)P۞loRT݇Qk")ƫr&9+8fM<4&pڷX +$r-M3+@՟q!`ww;Zn d=fq ` +,<,PZ5I]gƒ X d%\x)@S]ɜ=xr, ̐[mU7 h~ +'@%}C ǰ!Xvu,ğk,x*t ~~>|jBY*ìOWhd2#jAN-gyJS>_4 m3P]ښ]H"ƛ6\֧pj*ؤ*sSc"¹[y%iտOoh=c_==]<3H ))h +?{yH"Ő9B%o^8CӒ( |T5HaWBʗy56~OVMJz-Th7f'2M̾#i\CJjs='J\ TAK$;)tgĪlJm 4V]\$8ݬ +/|>\PZ{{`g-_,T쩁Pƛ;KpڪaɉCQÉ$1@(uH8j WYCS=jc= F2 &gHgXgv3r ziyYd^U/~޸>5vM\~T[2)ϋP?o{0-1Vo4ZY8썩 ȱ`}uQQ-:M|nrs;X--cL +:...bK7 0#j/۩1Е⍂ɋ-^ G_G֏e,XiΡ% b\."s,T9h>nyCs.l ft7}&+NbSI5ry!FR R 7i#&"p=7Tgf$0曮fjVdFc˞myS#ff7,qBFRn= 1XVKRX_-Ѿ6%Jh$$C`"ϙ!g] GXӭ/Иo dGM"dmyv/)=EzyXH{v.4''s.k!Y;A΅징ݨmf?o"c{w~Nt{o688u55\4WZ E)SΟ1du + y}Ԧ'c,˪ܨ#h="C_,j;[IxSa;lX|3Q}P,4l'|| M Cg9>)X'0a)賥5®:^oa9lgZeK8) ̻k^K@KUߐ')DkY7UҌ`~{So#r'<CY%S$yJ.AoglHкfW«J=,Q/pOasRͥ&4:srf{츠۸VQV%.T)P+$F^$J9@(p9+kJPE"J5;{wZt{ЇJRR*> j4=t`+gQAKg1 $__Cׄ¯Uqe_'׸ڃF5)'S~ +=kǭ6R7T\N_Ɂz J>W[-a0'I8cJ%Flρ1 ~!<bwfWa[]&0;X>fxr(# %Q-LiA䭃6^EXP_̠;S{Fϼz~d<55ijeD?4 WjynBTGIqD;M^4O7>x"pZ{6g[bZceJ^`O+M2O|-N`Z.hIK(,RuiOw +E6,v +A:`>j̷D! MNGqfq6}M9?@XO@if-P5ڑvbэYa!\z@.4T"e(aji2prKRߓtW>zFbna5^C` c8pYo/?ֿԳc=,9뷩]k3pdz zh`HwnCڜjH]Ӣly{æX?C$2gg7 1 =@*[ۦ8`㴰/ k<&j!OLfJS`7e?RS; ɮm*bX=f]bw0Gs9~{;<^cj9웏Qb2͇7r%,kjm閟 >,erT7| +8$Lb}!V'77Wں6tmGV\vgಽ eXNnIv~{^NIkNJ}]خak4Tq$OWG5xt9^څW`D:FjkVHԃe±A:TׁmpQ]XRPbri1w>':F;tue ,.v=ٞuNQ\TsMo0Ks[ݭ"uwɧwz٬=>ee>z]ړM&ujLlsa5fs"o9o&hz[;^ޏ@O3^_4}}->:7%vfa +uK?r{1p_"Y4՘3ċ@BS ׂ^=̨U@fEF}+RBzr'P*e8Za)4 #tK/jI LO-K#)\-+bH85F*Es>2z)G˽>j%WHFR-e(1G5}.CiԩbjWWq_<ޝwgw=6!&'&L+fW2,E㱐4MCPS"ڿi>L'fS=PK1tcTU;3YUzllTPCݤ(&s=/ґƼ筭b$7$d"Rh¢GjP v  y<~W + 4ͥE^6N9L$˭Y~OToĽ~T*Aj/gB+'k#2Ԝ LSGzSݟ٩ՀQgatԐFkthcSi\nfqjڷTꁥoEZښgyѴbҌD+x^h;3}kJ4 +]7>/~K"gon\CUyҳi}԰= N +[^Re ^6iS~5O_Ǒ)A7kN3Wc2vO,暻ԏNpշEͱ,+XвJ X$X6y> +U'{l׽oϛ&l^` i:NHxgُB-Ƒjm + +݇-15rq"fvٗ=t-Yy Eau!0OO%Nod +E@:"`F!~ s ޙ3n@ 3W$k=Uy*0S8kZ| +zTeԷ-`9@ҪCMVL_bH@9K5q4u//вX +F8()-I} ~E۠8ŭG^Q4$2 +\Icx-iNx);P + udB@Hg :ݷ8/ntlRhQ:p#jR?WL M͢AF'~՘ H~kϩD,{DS@-$Pr"a @W3GrCu"0/h Xۭ(U77p P@ƴ^UBD|9)1 FI-l2Ǻf47=OPI ^-yq|Wii_\ *kх:M\Tƾ?ՀJlőq<|knFӌHȴw[?HwƱ MS`MB4k60qA_ؐ8pw*@ݦ0Pu&$U޿ +4{Uyl]b4 4fwnj,?5׹ZBvEgm%}1s&U@IA(&0@/̞: Ph9@5N#*`u&P[O~ Q9Mڑ^eUQ] d#NcfEK j$v +Sy1g^V|8.[? +E:B6Oc 2-`?{/F8E0~ +5'3*"Hƫ͉IO\qnDO:d?X=e)Kd1xBܮP⃂C:ɱ +0^ RH/Ni0}rEb+o8:?+i8SZ9x2~XEls{!ۡ.,dfxǻB.gPLkI_JZ9wVG]϶ի K(GCe4pB[PO/lcpE/]Q"waꒌ93Vm܎\ ;kJiE5ܲإV  L]FR,ۑ?Fp&^IZƫv.c{ImBTBdfԝfIJbKߕ$}*qd9āmu4uJ+,i҉AgQPܤA?j.$z+6]L8wu0b,1&e1I}clv7hsv\spmLzB1"!9-*H2ʱͲb{y9<%w8#(#8v0\"X .g `.CDJ + a[nl;prgٳ1]&^x :zSf&8-3mLs=aP׆C7+W|-꫄?(!I +VG{7ߏtEu~,{:JY +gUHa>gk`]Vh^>u.΄q~3\%z0xڛO0\؝}! +7}DeK 0 P1fhmqɶ$Dci1`VHÊ(3"hqK j5Տ6?csh #<.7Q^v\o B=h!#vRhfbpIK|ds)Ė< '#jF.LPȲrlo/'fؙp w:,$d?fܵc"|6݋Cλd?+,-9!e:3w銲.8ʣ r{>]Ȏ]9}=,n'[θ._>n}Twul-֕kKh]>]ț 'ύ6ׅ]2e\m( +Y9&E2Ihڐx2KVƕT6m}M EbЉWБgw|A -PfOj+訳;;ݙ` ?s5ttjj+/c<tp'+j+.c'$ rvpVL8LgPT[xl39YTSMa$T+`K ¹xS\EifYgHgֻ'X3"]9'~v"U4wģAhTK! ߛ$rklǧv1En$䦂P,X&;b,B+2n +9p08y/B3v +Y A.2RbS?Ĕr{3d=&AeJ?8ː+l93&6L2Uia UiGqs:> ,c'QY5h%\9F CMi9`P9 я[ǧҹnj`ּ]Y #B"ݙ)b&dd>5Ic[![;#Nꧯe9%ve v!Uu_ltZ(GB:ܵ-+݄vEt3"Z/ 2,zN X;#npڗF#2xbUrMr]W0vsx١<NJ䩱ؑ•>[%Ӏ dJ(nѧ10$a^oF˕tc`P5sce8rH Sr}x"2 +;x( I(Z8p"2.m`/1pآ0^u6dyZ4<<5HOR8Gdd$ N _<+"23$#*Pso } m,N|Sc%M#X&1Fj\߀\^Ksgo6BCŇ ־J{SXօT]ݧsׯZ $KGdT=2Q@87#7#(oOz8~_1z8"U)00uTЏl)V +(9+*RE&.:q\ "%m2 70h l[X;(b_^ +',GJxVqRN)[ ;)cDEJPz_vXm'ú:V -&VWj,}Uv:f|`2,YI" I@ijHNFq3gH|qɺJGq̺97| +]%iD)MGVj&B7 Tlwo/vdy: uRFdO۱+Qf5M՘+%j`.Ẏ +%Kv= +K(T[(T{~]j,H[deWM_r+< xAZ$Ra`vAHTc+7bXr_]7WteAoء}޹ Z긂'd;dGn'Ғ˹I`Z=&QJ1}͹*,fy~`+ҞD>)'Ko?o#ݝQCUCDzk"q8;O*3:yZ޷./r'yوiCqψ\KL^/;okȚ/㫥lFp_YM'j:]Nj::G ux5t+j>uBh5_-oCt|4j:>K +η|5_9f jDιd5_1t|ʈyCT /j:Z:s>YMo)}u5_WǫX]I)j1&N.cu%]ej:Z:,OOW];ޛtyJ_\M~u5߄_^MG!t~U!CF%םt|;H(~sYr,jL܋wW ƓnLb^IPZ[W'׺x:fp|[u!O7½$Yx4,Yl'ë$oI[Q i/rlC튊 J;$(%'\xr%LC#9uS`죫 φI5ůgD]%pݝE ';*ΛR]*܏G掊(J\t'khk\G=\ѶE9WYDuYůB&7s_ L.h|xj|r uB_L DzVA@'aC]Ym/c 'TSQ_QX;f̹|-~/ w_}{Wt[t-C2|+Ee ,àQfɊTF2&YEjn;>6{vj :ϲԒEL41I@x!R@'|va(Ur CxEn1 v;e=0){U@|Wk.%iU,.h*ЕWVmWn^xIW]ػOIW=hՋ82#K~ODQEzzZ4缭B2a$ Gq6gţ[GGؕS~g։Kpzd%V=%4rUyM ŊAHNiJ M;[ֈY7{؂(4(aSKӈjgUs.{ +mo[sRo ̮/ዩP\+ë? MWYA^//lI'?|.DcHAIVnSjGɒ7t35Wu +ʼnIqħ8i,{n1JFH>ļL-_w]7>Z7$2$z3HR 'UrJ)dh +n*vr$r{R2R6eB9ğK] +xWzӊQsخ}wV̛•xMm_qKپk vy^_<-#Y!U$m=1Nعb0c|dN8`pY7v!> +;B J Ȋ@eW(G9gD0ǯ9Gʏ7.x΁ k}G,{;~_v)g]NmFV03oITYbt&y=Iɟ$0\{_\g4tʛ@S5F=UʭTF]/߇*Bs[PV>yOc1?PegCɿO+u6l({~5˹O׏9[x]N'//#yJ^?YЏw![>yߝ=|-V?{d'N&|~9W>v͵~^?θddt~4 +N'\_b'* ;ǵ-?{''+G'>_pfAŽ~1N|^?VFCs/ϧ2;;?b!O5YDo>~7'OoO*:Q{D-T]9 +!i{w#X=t"v@n[>> INϽ~wóbJ_ Տ ǑcO<щOM"v*s1{x%oUu~{j񱯸׏5iw{V Zkd׏1|`/R'~'+VtJnVrTuSz6ϡ]6rpB{o/ A-v KZ 3E +˅ d¹ϊ\q|QX2M'a(]ܤt !2ωæ ]ݲ'Zcw 90G*4zfg׭͒_mfc[MKꭲ(@2}t8k.u;(:8%-罺YB%v$tM \Zz]L/ILuAD5y-cHFLA]V+ XFmi1 N\EWZF>Җ҇ˌTdFa^ *+hե12xxrEАh"Q1FE[AĶފ[)ZgzzkzsMZ3[05aw@_Z.LBg1,: 8@T jJ2ϛn͊BsO\ƼU?wly9}9ggIS˕-dH2Iؑl+l-'8۱mJ3 @`ղ@[YB+d{{ғ,ky'HzW;]-K@тgQߘ}m=IldНې{W2vl<᷀CV?MIo!HmVanĥ54:R-CEچɾfׅD\k]{j0b qE])UX,IShs]@4BO +s%$zi%inm^$iK]4.~e !?cjsQ=KH|<,ojj(< 5IK{ZaWiҮ* d--N$ޥ|dk4YRɪ8Dgcg +C_5O^ +FRS#NKL3*ْl=A[R5utE˖5nm=jחis2ٻ"JvRNy[I+Fq&etAӝhB5MJͶjҚ4p]`򫪝/}i^fE| .%:ږ-*,u/AuudshQ;^r/+YZfAjkV(Vf{4 7->>59kʕiK12Ӌ Ҳ3Ak pFvQS +7-ik._ے4x!*1[;65\U$ɶʩn&=1;lGVd1]y2GCd:Y) iwQEY=!rAsNX'ê2ofd zS&f`%J28(ĜQ E>r [OBRY20=Qp$v*ԍdL2O/:0/"-B3'ܡ-, 2J:ʲ=WaІ\(,]{ˁk<i4 ++$&dYz}!zk$/j,ImDNZl9vKhufXԣ&D0 Sy%O(/C~0r#jQzQAH%/*h;cK;ӊ+ظKqr4XBJrJ˲ 99"uEB^Q0{hBLVZfP bq(9}CKp5mMY]MyDyP> :,wՈH8sζ@(KOB__0bVLl \]`(*z09Tf&{ȒXɧnyI)Ln^k~Ks[_@^/b},3E=B32A=͙Cux,&eIݵ]UˆR;詥Kt練1]ʯcqTak$,Mhpq̖wo;ri*PEMkK>&h4$&1h%~2]OFv%9@[싫Аb4ǣUPlQ){Ѐ.mr P VG#̉/΃У-X3Y# H]G%ճtN(^CгX8E䰠&>M8YLYj\e| !BWRK3qgFJnBu%n~R^.dP%i)h3ry}xؘ8ssQSm ۖe&q dhK2H2%Y+RZKS3*1/}4?/31NP~ Lhl91][dP7)Z,F{`kRAŖVbW5z/z wBu&uHga͑^q4GfD$^~ V}hkzcD$FO{!b͞oF:5foXOLcm+)$wڏy MApKwpgi#sUϬlIui(j\EZRMEv³.eޥ̃*=~D?V3F[PK/8B]RwKV1QsGr3 3 &\:\j)H|@zI]nѕW^WB=^8Alkܢ~5Br~oeS V*vɱ-^YQFn,OȬx"#QFJ{_G)w\fBx.OD uCL + [[ިB@${@ htC}I;@ՂSQ#n|CВVↀDmFJi '  ) ~c{-pAP +;eR*2U﷊P3S%̈@d{V4a7Za-#r7:FBjeL]:Uū(݁c󤣲Ы~ R)kIRAxkMuh*jA1ל_b~*Bs*6_zF#~K`b*jV kes@@~K}ɁߢW+:)? 1Bk$mJ@C/.LQ5Hף= eDR!=HO\hMGpd%:2SPp_`R; ^O-Dgn(^ +sKHROaI0`Ծb?ߺZrUФ<VVJ@G2G+"~z? JH J\mlGYzMEW@Sb7,n-xE69ђ / -]˫KG//B/W}&k5zK$ +"=ݢ8{M5B)V}޳uuE^ac;* ҡ}k5IU V;oԑFJkz|d/5Z&QŃV/=+yM ]|AiN+ZD;K>GF3RMӦ8H#IRY R_mS +Yi 7RUhHc`\м*O.hȅI1^yʱo K~K:>x$4Y>zꃗeQ"'fGhoPPwgwnJ59(`㬑ѕ}##{inQuw@qH+N(n=c/ +- +{-yɚ g*NQ@+,I)͠bѭ-kLQcG+~`S7lE N!9lGokCFvqfScgV4'V\F89humJI4ϝ\ ^||?}R3HZLjQ"K}E%:D"pvd:e\.~p{|W+Pl ҈\E3I3%k/4 +9<}=!g mJIV&('QɺNM(q˒맜(3P䰰[rp~W,+JVGP(+&!@|z5潲r RJUՂ_C7G CR"pFqX,M@clOHHQg9) T~c{n2bd(1HgCOh:s77Q1ҿ¬$dȩeUXxT4Ů1M C pG$}GEE;`],zpxg<갷xg1;W{w1[갷C{wUΕ:-^|},;oָC,F걷8oxgQb +|oxgQE,ҽŐ7[ bEfd{w{wo1pcK,0#뵿bEU #[ x{o걷fp{w##f,}-YTw"[ GxgQ#Gxg밷xgQ"S3-0.ѮVv&rkϾ_% #ՖYLRWhvMCVem1E6Hef}<>eq{'u(msW}@5.)Nws#eq bgѸ2SA,uŴQ՞Ke +eYKr2-[l.;- Yz䧮1bJMmx)uzp-zEi]B^Os-:W{2/y4U[)*,K@)+Z[rq'>c}fIAsUMn^h+(_\%+95 \\/Ay?` 4ZMTQ\:iLVw>·k?*Ia*&a~B9:ҠbK2;^;vtW>a=% b нnYUkW]-Z hj)T`y#Dk7Y=(&]^"`%xRYb>9g,|"r$DS[L Uf!\o~fHbs]inG+7$K8aAG +)vLjy% Y9"E(# pj)7=yfYh7$]!)DH<$%,B"D +4%1cn3ьDP,((NbY\:K"#q`RGo3+"X +0҂& +JP+ +$M \ͳx!@? aE,W^ Q;"63^DyQQH0 ߑxF"`zd@X234O9Π 0 &63,`JIhUR0t5D0" +'C"yL3(^ͬd P)K ( +C#CӐÁZQ +3 ˆ/ O_bː[F 5Cps w$b;hPx * %B4r(ND ᢀZP:`P`;S%vAO8<(ErUsE|"U=*x9e;SC  NCl.DvF P*$KP ${CpVEz Ҽl^q,&B,΢~r +&68H0qr+H2Ϲhq<\+G&'7|E/4[o4 T;L&4Fݽ) @l&G^Ǚm9Sȋ XH!y3;6|Tw__T wP!9嵌9CT]4{C 1db'X݊2I6+g @BЀQ+g 4`40PG 1!ph`L4a 0 `Ch`h`S~`04\ #1 V%J40400B.O16xHAmD$)m"hHa_"ц!|6rn6KsKS [Dm<$`)цC F8m̼%x,[G8m̼%x,̇D$hhD-mHBh[JmJ A UDmDPG10E @]0r1E uP1\ #0r10E #p0r10\ #p0r1P '\ #ň0B1P(\ # #0B @CbNO10PBKK #40Ԋa6DO #èaal0N?' ? `AmhHMmJ V_60!H+&6T%ڨ&FˇD$hhD-mHBhC Im%x,[G8m̼%x#C Fmm$”h!#L6ln6KS$0D$OhD9E CDm6 ICm "hgDH%}1vuh`h(abF.`bD@C@/1\ #0r1\ #0r1\ #0r10\ #C]0r1\ #@]0r1E u@]@O1K 7C $DcR tQG L6t1L4yIsQ k4)SN:c'M6 +4M3M 24mʱ&JRuTӬt'=qi*V6A6ēOYqO:qƴ&DVu̹a䄘S̘v1O>i東erN>eQj tzAIEUU19׿c)3朑f/kj+sgLtIǙf*9?YXE[O5m=FpqdFIʳ/UW\|֊"1z:c99uQ~uͷx#9t%#x6ܫn{ʳ*I:bs._W?ʡbt֥ hIN[ky᱿ oS7gN;ŗ^^t_zxۯ<_p)t;z?zqet<_rӯ^7\SEOԏ6_O>wNGij@;~{zǕg6i1/ر^|_WH/wOzo_]|^Ǜwڵcןo~޶XJ8uT]_ڳ{_yOW֓fc.nݹw>ndeUâSF7?}vmͧeiI}!}vnYw]}VB!Sp,m۵;6~C]>_>ྃwƋP>K>ь58th'%zuDMg/E#{ÇܼncwOЬnʌf4G>|pߎ¦+AA;~i |a <{&Oyr,Ul@w7l}7>ˏu%(SϙR4|)~s;7KP1?l~u_5Uڻ]_¡5s^#}U lغk#~=t%(%H6 ^{_~{̵eCSLneyW!-ҵMzJNDziWTFo sHPz{|mݦ{sKPz:;yϿU} EG[xOܹa] 3A^{~u䐮+AGC9[G 0\u#(_C \mB!W#ߩk\gp#=dkU{ ZwI(!tasippqш{s u]jtz`]!ډӧM9c52y2b = 5Pz.-mO=stF6&O_[`#~ӥ S zҼn3'̜~T@v݁b]5{GZd₸X,8乳g;Aaƒu%2K($6崓f;0 +?G|%gv5T.r$/7'+]b ;0s? uYG=us:,(['[O3cj$:BmsW9:2쒡ե)+ڊ۱qĺ}++e^xhOsEa +{,{,"8ޯڗ]}wom7_jmg藗GP6{vlw^{㏹G/l.po׎?]o}e͚W^zɇs3`;/>׻eݺ>x5>.w +r,(厁zW۶n-[lwwLV}!%(߼~v%GxgA?Q޶Ueɪ_}nX}݅+ P|LMVCP_~ũv\4g +[!>r5tVq>Yd5n}9t:.s +Ӂvd5$U+l-I',fLg0T4G(v$L T,> +AJ%_sf?Z@N'ŐޯtKCR,5[ą C龧XʹAOP*1\ W=K  6Dߡ!6xY?t ZJ@}rhmuVR < {Zi^/]{!Pƚ7l޶c]4={;}}ޅ̯^4Ohɺ5biuy7?MlqdnՎw7>\!=]W/r*Ͻzy;|n:O>[{yw̝y_ht5e]?˯Yw7`M۾s`LJ]~EgMٿO{G}5z'zW־_'A}USTfIcKO&yן?[뾀hXyߥ Aηq9ο+\+7q?|vjAY'[Em]C9>ҫnּ7& Nb}%NqWUԺ*[:zϽzїʛ}ώM:}IZ)(,*^Rݲ_^[5OrSM2?6>NZZbX!%˹ek7y +9:KV{e/xrVvG㻸57`U~o_!mGף';usrƒ7?ݶ!Ou?UG+{/ϿHNʂ r[~6~+Tr +B/y̫DK5WN8-1lS7trWQGCjOdVs+ r_y5:]4. +\aSn6WP/s} 9\EHN~t4.4[>$ao''Ysmb._HP+&!w˗[1 ]`$t]s=. ߨ4Isv{j|s䛠VLBn T=~⚲C^9ԊI&MyJz[vwb.0ӓ:ϓG:t лu̱902ZyM.5tM2$+[X?rşyMPD@z}Uѓ:5AZsy k߁A[H(=ֆm߻syߥ -Pb}{yߥK!35mޱg혜.LPs~|=[8$|r}Ͼa;};9ɺ۾޹}:}K6hW;oT缯Ҥ0M۾MJDwX, +˪jk93y3' ,FPuN:j8gĸ:~ 9fӬĞq3'tʎ9{ɧv)'͙5}w&7mp,t ;ML66mOQ7&MF2iB5D6:Z>kgB5izjkki1U;M" ޗ t JL&GH^wHw_*s +|D-JK;åm-6ʥ&ҜaJM$,s Efx)IbLg$)b-\)( 4J$% HMc-T2ђDH I/R Nx9J\r<9 F"eR$ W$IUCR,1#(%\PUɐ|!;ȺE@bIМ(kb(< D @&Z N$\,SA{]Ю GDpa %A@'rM$X:xQjgYJT=** Bv3< IW,'˒*PD h0Zm *"/%u JUFJuUyNg"Dù ZIQω HJPiXhj~ LQ@_`y!@H@(UDF2,ùZ@P<>AV% P'4EDA$ h:".@Bl>NEQT4Jym+P"W.QK5*AJ\Ama$aH5-3Q?dbRDV/\#E@DCLp2Ŧ*"HDaI.A@tSdkG qBtNB@i`fBhJ ؀Be(<PE44/"tGq;zN -$(`'(yAEYYIǖA%SZ $4G\&Y!@ )z0fP3 YJA) eXfi +zHe!9.)дQ pFȱ‘$6\T?H>0DFipS@q % +HG@C.@8\I Zb`Sr E A&%E, 6KPiښk^ hZx_"!${&80HpWD(d \o(C-4.>{>K<4AsH +f ȸHC"m 9єqQ-(#J !p^-iC#b  xfLFՁ>YU嶠0_mB S ζaC$Fl^fL8 *`Jf3<(CeD +`({^x4"2j`$X<99Dxb>-c2\Ɩ\298v2P+[8c y +2?!ZWQA@8B;CƵ8`D ~>' U1.}l#;?[MPE+M&ɜ`3W/WDB1<k7UkZ#=$05d?hŐ$G EaEu)c 0+J`a.F%"ДR<Tro` _. Xu4\v` $AzfRv=ۆZ Eё~<9LKG: 9!)Hj*m:iQǪ&аf^NAپ>l̿eUPrj-kj2uani+ΡpW'՚_Z`[{ endstream endobj 276 0 obj [/ICCBased 285 0 R] endobj 5 0 obj <> endobj 6 0 obj <> endobj 40 0 obj <> endobj 41 0 obj <> endobj 77 0 obj <> endobj 78 0 obj <> endobj 127 0 obj <> endobj 160 0 obj <> endobj 193 0 obj <> endobj 194 0 obj <> endobj 229 0 obj <> endobj 230 0 obj <> endobj 258 0 obj [/View/Design] endobj 259 0 obj <>>> endobj 256 0 obj [/View/Design] endobj 257 0 obj <>>> endobj 222 0 obj [/View/Design] endobj 223 0 obj <>>> endobj 220 0 obj [/View/Design] endobj 221 0 obj <>>> endobj 186 0 obj [/View/Design] endobj 187 0 obj <>>> endobj 153 0 obj [/View/Design] endobj 154 0 obj <>>> endobj 120 0 obj [/View/Design] endobj 121 0 obj <>>> endobj 118 0 obj [/View/Design] endobj 119 0 obj <>>> endobj 70 0 obj [/View/Design] endobj 71 0 obj <>>> endobj 68 0 obj [/View/Design] endobj 69 0 obj <>>> endobj 33 0 obj [/View/Design] endobj 34 0 obj <>>> endobj 31 0 obj [/View/Design] endobj 32 0 obj <>>> endobj 267 0 obj [266 0 R 265 0 R] endobj 300 0 obj <> endobj xref 0 301 0000000004 65535 f +0000000016 00000 n +0000000342 00000 n +0000022738 00000 n +0000000007 00000 f +0000104269 00000 n +0000104356 00000 n +0000000009 00000 f +0000022789 00000 n +0000000010 00000 f +0000000011 00000 f +0000000012 00000 f +0000000013 00000 f +0000000014 00000 f +0000000015 00000 f +0000000016 00000 f +0000000017 00000 f +0000000018 00000 f +0000000019 00000 f +0000000020 00000 f +0000000021 00000 f +0000000022 00000 f +0000000023 00000 f +0000000024 00000 f +0000000025 00000 f +0000000026 00000 f +0000000027 00000 f +0000000028 00000 f +0000000029 00000 f +0000000030 00000 f +0000000035 00000 f +0000106611 00000 n +0000106642 00000 n +0000106495 00000 n +0000106526 00000 n +0000000036 00000 f +0000000037 00000 f +0000000038 00000 f +0000000039 00000 f +0000000042 00000 f +0000104443 00000 n +0000104531 00000 n +0000000043 00000 f +0000000044 00000 f +0000000045 00000 f +0000000046 00000 f +0000000047 00000 f +0000000048 00000 f +0000000049 00000 f +0000000050 00000 f +0000000051 00000 f +0000000052 00000 f +0000000053 00000 f +0000000054 00000 f +0000000055 00000 f +0000000056 00000 f +0000000057 00000 f +0000000058 00000 f +0000000059 00000 f +0000000060 00000 f +0000000061 00000 f +0000000062 00000 f +0000000063 00000 f +0000000064 00000 f +0000000065 00000 f +0000000066 00000 f +0000000067 00000 f +0000000072 00000 f +0000106379 00000 n +0000106410 00000 n +0000106263 00000 n +0000106294 00000 n +0000000073 00000 f +0000000074 00000 f +0000000075 00000 f +0000000076 00000 f +0000000079 00000 f +0000104619 00000 n +0000104709 00000 n +0000000080 00000 f +0000000081 00000 f +0000000082 00000 f +0000000083 00000 f +0000000084 00000 f +0000000085 00000 f +0000000086 00000 f +0000000087 00000 f +0000000088 00000 f +0000000089 00000 f +0000000090 00000 f +0000000091 00000 f +0000000092 00000 f +0000000093 00000 f +0000000094 00000 f +0000000095 00000 f +0000000096 00000 f +0000000097 00000 f +0000000098 00000 f +0000000099 00000 f +0000000100 00000 f +0000000101 00000 f +0000000102 00000 f +0000000103 00000 f +0000000104 00000 f +0000000105 00000 f +0000000106 00000 f +0000000107 00000 f +0000000108 00000 f +0000000109 00000 f +0000000110 00000 f +0000000111 00000 f +0000000112 00000 f +0000000113 00000 f +0000000114 00000 f +0000000115 00000 f +0000000116 00000 f +0000000117 00000 f +0000000122 00000 f +0000106145 00000 n +0000106177 00000 n +0000106027 00000 n +0000106059 00000 n +0000000123 00000 f +0000000124 00000 f +0000000125 00000 f +0000000126 00000 f +0000000128 00000 f +0000104799 00000 n +0000000129 00000 f +0000000130 00000 f +0000000131 00000 f +0000000132 00000 f +0000000133 00000 f +0000000134 00000 f +0000000135 00000 f +0000000136 00000 f +0000000137 00000 f +0000000138 00000 f +0000000139 00000 f +0000000140 00000 f +0000000141 00000 f +0000000142 00000 f +0000000143 00000 f +0000000144 00000 f +0000000145 00000 f +0000000146 00000 f +0000000147 00000 f +0000000148 00000 f +0000000149 00000 f +0000000150 00000 f +0000000151 00000 f +0000000152 00000 f +0000000155 00000 f +0000105909 00000 n +0000105941 00000 n +0000000156 00000 f +0000000157 00000 f +0000000158 00000 f +0000000159 00000 f +0000000161 00000 f +0000104890 00000 n +0000000162 00000 f +0000000163 00000 f +0000000164 00000 f +0000000165 00000 f +0000000166 00000 f +0000000167 00000 f +0000000168 00000 f +0000000169 00000 f +0000000170 00000 f +0000000171 00000 f +0000000172 00000 f +0000000173 00000 f +0000000174 00000 f +0000000175 00000 f +0000000176 00000 f +0000000177 00000 f +0000000178 00000 f +0000000179 00000 f +0000000180 00000 f +0000000181 00000 f +0000000182 00000 f +0000000183 00000 f +0000000184 00000 f +0000000185 00000 f +0000000188 00000 f +0000105791 00000 n +0000105823 00000 n +0000000189 00000 f +0000000190 00000 f +0000000191 00000 f +0000000192 00000 f +0000000195 00000 f +0000104981 00000 n +0000105072 00000 n +0000000196 00000 f +0000000197 00000 f +0000000198 00000 f +0000000199 00000 f +0000000200 00000 f +0000000201 00000 f +0000000202 00000 f +0000000203 00000 f +0000000204 00000 f +0000000205 00000 f +0000000206 00000 f +0000000207 00000 f +0000000208 00000 f +0000000209 00000 f +0000000210 00000 f +0000000211 00000 f +0000000212 00000 f +0000000213 00000 f +0000000214 00000 f +0000000215 00000 f +0000000216 00000 f +0000000217 00000 f +0000000218 00000 f +0000000219 00000 f +0000000224 00000 f +0000105673 00000 n +0000105705 00000 n +0000105555 00000 n +0000105587 00000 n +0000000225 00000 f +0000000226 00000 f +0000000227 00000 f +0000000228 00000 f +0000000000 00000 f +0000105150 00000 n +0000105241 00000 n +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000105437 00000 n +0000105469 00000 n +0000105319 00000 n +0000105351 00000 n +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000040170 00000 n +0000040261 00000 n +0000106727 00000 n +0000023248 00000 n +0000024631 00000 n +0000040806 00000 n +0000031733 00000 n +0000040575 00000 n +0000040689 00000 n +0000025673 00000 n +0000024695 00000 n +0000104232 00000 n +0000025109 00000 n +0000025159 00000 n +0000025997 00000 n +0000026683 00000 n +0000034475 00000 n +0000026061 00000 n +0000026729 00000 n +0000031770 00000 n +0000031825 00000 n +0000034591 00000 n +0000034657 00000 n +0000034688 00000 n +0000034978 00000 n +0000040057 00000 n +0000035053 00000 n +0000040457 00000 n +0000040489 00000 n +0000040339 00000 n +0000040371 00000 n +0000040882 00000 n +0000041060 00000 n +0000042342 00000 n +0000066380 00000 n +0000106762 00000 n +trailer <<68134F37FD7E49D4AC2BD09032E5CDBD>]>> startxref 106904 %%EOF \ No newline at end of file diff --git a/doc/ci/logo2_50.png b/doc/ci/logo2_50.png new file mode 100644 index 0000000000..b8ca08fdba Binary files /dev/null and b/doc/ci/logo2_50.png differ diff --git a/doc/ci/logo_2.png b/doc/ci/logo_2.png new file mode 100644 index 0000000000..dc0d52aea5 Binary files /dev/null and b/doc/ci/logo_2.png differ diff --git a/doc/ci/logo_2.psd b/doc/ci/logo_2.psd new file mode 100644 index 0000000000..27c97610ca Binary files /dev/null and b/doc/ci/logo_2.psd differ diff --git a/doc/ci/logo_2_w.png b/doc/ci/logo_2_w.png new file mode 100644 index 0000000000..40d86c907b Binary files /dev/null and b/doc/ci/logo_2_w.png differ diff --git a/doc/manual/appendix/create_datasource.man b/doc/manual/appendix/create_datasource.man index d3edfb692f..e196fd402e 100644 --- a/doc/manual/appendix/create_datasource.man +++ b/doc/manual/appendix/create_datasource.man @@ -207,9 +207,8 @@ Proxool(严重不推荐) Java代码的方式: {{{ - //创建dataSource,以DBCP为例, 代码仅供演示,实际使用的话必须单例或使用DaoUp类(推荐) - DataSource ds = new BasicDataSource(); - ds.setDriverClassName("org.postgresql.Driver"); + //创建dataSource, 代码仅供演示,实际使用的话必须单例或使用DaoUp类(推荐) + SimpleDataSource ds = new SimpleDataSource(); ds.setUrl("jdbc:postgresql://localhost:5432/mydatabase"); ds.setUsername("demo"); ds.setPassword("123456"); diff --git a/doc/manual/basic/encoding.man b/doc/manual/basic/encoding.man index 6d328e96b1..20e6c46080 100644 --- a/doc/manual/basic/encoding.man +++ b/doc/manual/basic/encoding.man @@ -30,13 +30,13 @@ JSP页面编码 -------------------------------------------------------------------------------------------------------- tomcat编码 - 打开 tomcat安装目录下的 bin\setenv.bat ,该文件通常不存在,新建之, 添加如下内容 + 打开 tomcat安装目录下的 `bin\setenv.bat` ,该文件通常不存在,新建之, 添加如下内容 {{{ set JAVA_OPTS=-Dfile.encoding=UTF-8 }}} - 打开conf\server.conf, 在8080端口所属的Connector节点,添加URIEncoding,可解决大部分GET请求中文乱码的问题 + 打开`conf\server.xml`, 在8080端口所属的Connector节点,添加URIEncoding,可解决大部分GET请求中文乱码的问题 {{{ URIEncoding="UTF-8" diff --git a/doc/manual/basic/maven.man b/doc/manual/basic/maven.man index 89e1904fba..ae62bdf487 100644 --- a/doc/manual/basic/maven.man +++ b/doc/manual/basic/maven.man @@ -10,7 +10,7 @@ Nutz核心jar org.nutz nutz - 1.r.62 + 1.r.68.v20190220 }}} @@ -40,13 +40,13 @@ Nutz核心jar org.nutz nutz - 1.r.62 + 1.r.68.v20190220 mysql mysql-connector-java - 5.1.40 + 5.1.44 javax.servlet @@ -58,7 +58,7 @@ Nutz核心jar com.alibaba druid - 1.0.27 + 1.1.5 junit @@ -86,11 +86,14 @@ Nutz核心jar nutz - https://jfrog.nutz.cn/artifactory/jcenter + http://jfrog.nutz.cn/artifactory/libs-release + + false + nutz-snapshots - https://jfrog.nutz.cn/artifactory/snapshots + http://jfrog.nutz.cn/artifactory/snapshots true always diff --git a/doc/manual/boot/helloworld.man b/doc/manual/boot/helloworld.man new file mode 100644 index 0000000000..d02f8ccd53 --- /dev/null +++ b/doc/manual/boot/helloworld.man @@ -0,0 +1,66 @@ +#title:NutzBoot快速入门 +#author:wendal(wendal1985@gmail.com) +#index:0,1 + +-------------------------------------------------------------------- +基本的环境要求 + + * JDK8 是的,NB的项目最低要求是JDK8,严重建议用最新版 + * IDE 即eclipse,netbeans,idea中的一款 + +-------------------------------------------------------------------- +使用Maker自助创建 + + * 请访问 https://get.nutz.io 按提示生成压缩包,并下载 + * 解压后,使用eclipse/idea/netbeans按Maven项目导入 + * 等jar下载完成后,找到MainLauncher,里面有main方法,启动即可 +-------------------------------------------------------------------- +NB的项目的几个要素 + + * 一个Launcher类,必须是public的,不能是抽象类 + * Launcher类的package起码是二级的,例如 net.wendal, iot.bigbig.erp是允许的,但不带package或者只有一级(net,demo,xxx)是不允许的 + * Launcher通常带一个main方法用于启动 + * 依赖starter的类,例如@At,@IocBean,@Remote的类,必须位于Launcher类的package或者子package + + 包结构如下, 位于src/main/java 目录 + + {{{ + - net + - wendal + - MainLauncher.java + - service + - TimeService.java + - impl + - TimeServiceImpl.java + - module + - TimeModule.java + - NtpModule.java + - util + - Toolkit.java + }}} + + 配置文件, 位于src/main/resources 目录 + + {{{ + - application.properties + - log4j.properties + - logback.xml + }}} + +-------------------------------------------------------------------- +接下来怎么做? + + * 请多尝试几次不同starter的组合 + * 部分starter需要特定的支持,例如redis/mongodb/ssdb数据库等需要自行启动 + * 查阅各starter可配置的参数 + +-------------------------------------------------------------------- +一点小小的提醒 + + * [https://zh.wikipedia.org/wiki/%E5%BE%AE%E6%9C%8D%E5%8B%99 wiki百科上的微服务] + * 做一个巨大的,包含复杂业务逻辑,包罗万象的单一jar的"微服务"是徒劳的,不如写个war + +更多文档 +--------------------------------------------------------------------- + + NutzBoot的源码目录里面还有doc文件夹,敬请查阅 [https://gitee.com/nutz/nutzboot/tree/dev/doc](https://gitee.com/nutz/nutzboot/tree/dev/doc) \ No newline at end of file diff --git a/doc/manual/boot/overview.man b/doc/manual/boot/overview.man new file mode 100644 index 0000000000..284735c116 --- /dev/null +++ b/doc/manual/boot/overview.man @@ -0,0 +1,88 @@ +#title:NutzBoot简介 +#author:wendal(wendal1985@gmail.com) +#index:0,1 +-------------------------------------------------------------------- +什么是Nutz Boot? + +简称NB! 基于Nutz的微服务方案 + +进一步简化Nutz项目的配置复杂度,将最佳实践模块化 + +可以理解为 nutz(核心)+nutzmore(插件集)+nutz-web(jetty启动器)的重新组合并优化 + +一键生成NB的项目: [https://get.nutz.io NB构建器] + +-------------------------------------------------------------------- +NutzBoot的目标 + +* 将nutz易用性再提升个一个层次 +* 默认配置应满足80%以上的需求 +* 默认依赖应满足80%以上的场景 + +-------------------------------------------------------------------- +几个术语 + +* AppContext 全局上下文 +* Starter,一个自管理的服务 + + 全局上下文AppContext + + 由于nutzboot启动的程序不一定是web程序,所以需要非web上下文,用于存储公用对象,例如ioc容器和配置信息 + + AppContext具有的基本特征 + + * 随处可取 + * 持有一个Ioc容器 + * 持有配置信息 + * 管理starter的生命周期 + * 可测试,可替换 + +-------------------------------------------------------------------- +何为starter + +"一种服务": 预配置,依赖关系完整,自我管理. + +* 预配置: 默认值应足够好,在大部分情况下不需要修改 +* 依赖关系完整: pom.xml只需要加上starter的依赖,相关jar应该完整,不需要再额外添加 +* 自我管理: starter应该有自己的生命周期 + +-------------------------------------------------------------------- +差不多是最简单的demo + + {{{ + package io.nutz.demo.simple; + + import org.nutz.boot.NbApp; + import org.nutz.ioc.loader.annotation.*; + import org.nutz.mvc.annotation.*; + + @IocBean + public class MainLauncher { + + @Ok("raw") + @At("/time/now") + public long now() { + return System.currentTimeMillis(); + } + + public static void main(String[] args) throws Exception { + new NbApp(MainLauncher.class).run(); + } + } + }}} + +-------------------------------------------------------------------- +NutzBoot与Nutz.Mvc+Nutz.Dao的区别 + + * Nutz.Mvc+Nutz.Dao是大家常用的组合, 通常跑在web容器内,发布为war + + * NutzBoot是独立的程序,不需要额外的容器,它自身就是一个容器, 然后包含其他容器,例如jetty或tomcat, 发布为jar + + * NutzBoot的程序不一定是web程序,可以是dubbo服务端,mq消费者等无UI程序 + +-------------------------------------------------------------------- +Nutz.Mvc及其他starter跟我自己配有什么区别? + + * 95%以上的代码互通. 例如在Nutz.Mvc的写法,绝大部分可用 + * starter固化了最佳实践,我们的目标是满足80%以上的默认需求,剩余的可配置 + \ No newline at end of file diff --git a/doc/manual/boot/write_starter.man b/doc/manual/boot/write_starter.man new file mode 100644 index 0000000000..b0fc28edb2 --- /dev/null +++ b/doc/manual/boot/write_starter.man @@ -0,0 +1,89 @@ +#title: 如何编写starter +#author:wendal(wendal1985@gmail.com) +#index:0,1 +-------------------------------------------------------------------- +首先的首先 + + 如果你写了一个很棒的starter,恳请告知我们,报个issue即可,码云或者github均可 + + https://gitee.com/nutz/nutzboot + + https://github.com/nutzam/nutzboot + +-------------------------------------------------------------------- +基本结构 + + 与NB项目一样, starter也是maven项目 + + {{{ + - src + - main + - java + - net + - wendal + - time + - TimeStarter.java + - resources + - META-INF + - nutz + - org.nutz.boot.starter.NbStarter // 这是一个文本文件 + }}} + + org.nutz.boot.starter.NbStarter文件的内容,就是一行一个类全名,可以是无数个. + + {{{ + net.wendal.time.TimeStarter + }}} + +-------------------------------------------------------------------- +Starter类怎么写? + + 首先,她是public的,非抽象的 + {{{ + public class TimeStarter { + } + }}} + + 然后,她通常需要读取一些环境数据,依赖ioc注入 + {{{ + @IocBean + public class TimeStarter { + @Inject("refer:$ioc") + protect Ioc ioc; // 获取ioc容器 + @Inject + protect PropertiesProxy conf; // 获取配置信息 + @Inject + protect AppContext appContext; // 获取全局上下文 + } + }}} + 以上是能注入的全部东西了,然而appContext对象内还有几个有用的实例. + + 获取上述对象后,你可以做到: + + * 获取ioc容器内的任意对象,从而触发一些行为,例如数据库连接池的初始化 + * 往ioc容器放入新的对象 + * 获取,修改,移除配置信息 + * 通过AppContext(其实Ioc和配置信息也是从它来的),你可以访问到其他starter + + 那,我这个starter对外提供什么呢? + + * 她可以不对外提供任何东西,静静地看着你装逼 + * 返回一个IocLoader,只需实现IocLoaderProvider接口,例如[https://gitee.com/nutz/nutzboot/blob/dev/nutzboot-starter/nutzboot-starter-redis/src/main/java/org/nutz/boot/starter/redis/JedisStarter.java JedisStarter]就是这样干的 + * 声明为一个"服务器",例如[https://gitee.com/nutz/nutzboot/blob/dev/nutzboot-starter/nutzboot-starter-jetty/src/main/java/org/nutz/boot/starter/jetty/JettyStarter.java JettyStarter],她启动了一个web容器,这时候你需要实现ServerFace + * 声明为一个"Filter",例如[https://gitee.com/nutz/nutzboot/blob/dev/nutzboot-starter/nutzboot-starter-nutz-mvc/src/main/java/org/nutz/boot/starter/nutz/mvc/NutFilterStarter.java NutFilterStarter],她返回一个类似web.xml里面的filter定义,需要实现WebFilterFace接口 + * 声明为一个"Servlet",例如[https://gitee.com/nutz/nutzboot/blob/dev/nutzboot-starter/nutzboot-starter-jdbc/src/main/java/org/nutz/boot/starter/jdbc/DruidWebStatServletStarter.java DruidStatViewStarter],它返回一个servlet定义,需要实现WebServletFace + * 监听session开关?web容器的初始化? 实现WebEventListenerFace就行 + +--------------------------------------------------------------------- +NB的生命周期 + + * 读取日志配置信息 + * 获取配置信息 + * 初始化AppContext,Ioc容器等一切必要的基础设施 + * 根据org.nutz.boot.starter.NbStarter读取starter类的列表,并将它们加入到ioc容器中 + * 遍历starter,看看是否实现了IocLoaderProvider接口,获取IocLoader,加入ioc上下文 + * 逐一执行各个"服务器"starter + * 等待程序结束 + * 逐一关闭各个"服务器"starter + * 执行收尾工作 + \ No newline at end of file diff --git a/doc/manual/changelog.man b/doc/manual/changelog.man index 664aa28472..e41343e1bb 100644 --- a/doc/manual/changelog.man +++ b/doc/manual/changelog.man @@ -1,8 +1,64 @@ #title: 变更历史 #index:0,1 #author:wendal(wendal1985@gmail.com) + +1.r.68.v20191031 + + * add: 更灵活的国际化方式, Mvcs.setLocalizationManager + * fix: Mvcs.getSessionAttrSafe在获取失败时应返回null +------------------------------------------------------------------------------------------------------- +1.r.68.v20190516 + + * add: 添加@PrevInsert/@PrevUpdate/@PrevDelete注解 + * add: EL类添加2个帮助方法,方便添加自定义函数 + * add: 添加dao层的LocalDate类的支持 by gengxiaoxiaoxin + * add: hmacSHA256方法 by howe + * fix: Mirror处理特殊枚举类时,没有正确判断枚举类型 + * fix: 登出的时候, session可能已经销毁, 但AbstractPathView不应该抛异常 + * fix: update:Daos中获取Table注解方式与AnnotationEntityMaker保持一致 by happyday517 + +-------------------------------------------------------------------------------------------------------- +1.r.68.v20190329 + + * add: 支持配置psql是否支持supportSavePoint + * fix: IocEventListener返回新对象的时候,并没有生效 + * fix: EL表达式在使用负数时的bug + * update: Images.read 支持byte[]对象 + * 配置项: nutz.dao.jdbc.psql.supportSavePoint 腾讯的Psql数据库不支持SavePoint操作 + +-------------------------------------------------------------------------------------------------------- +1.r.68.v20190318 + + * add: 新增Configurable + * add: Dao接口继承Configurable + * 配置项: nutz.dao.jdbc.oracle.ignoreOneRowPager fetch的时候不分页 + * fix: IntRange类在dubbo rpc传递时抛NPE + * fix: JsonPojoHandler存在递归死循环的路径 + * fix: queryCount时需要设置count语句的Entity信息 +-------------------------------------------------------------------------------------------------------- +1.r.68.v20190220 + + * add: hmacmd5方法 + * fix: 加个通用方法,控制QueryTimeout + * add: Dao添加更强大的fetchByJoin + +-------------------------------------------------------------------------------------------------------- +中途的版本看release note吧 + + +-------------------------------------------------------------------------------------------------------- +1.r.63.r3 维护版本,无代号 + + * 主要修改: 为适配nutzboot做的少许修改 + * 日期: 20171220 +-------------------------------------------------------------------------------------------------------- +1.r.63 香梨 + + * 主要修改: dao功能扩展,images功能扩展, nutzboot适配 + * 日期: 20171116 + * 注记: [history/1_r_63.man 香梨] -------------------------------------------------------------------------------------------------------- -1.r.62 +1.r.62 黄皮 * 主要修改: @IocBean实例工厂方法,扩展dao的map插入 * 日期: 20170718 diff --git a/doc/manual/dao/annotations.man b/doc/manual/dao/annotations.man index ea416d1f48..71e177be62 100644 --- a/doc/manual/dao/annotations.man +++ b/doc/manual/dao/annotations.man @@ -30,3 +30,6 @@ || {#888;@TableIndexes} || 表索引 || @see JDoc: {#56F;org.nutz.dao.entity.annotation.TableIndexes} || || {#888;@Index} ||具体的索引内容 || @see JDoc: {#56F;org.nutz.dao.entity.annotation.Index} || || {#888;@Comment} ||表或者字段的注释|| @see JDoc: {#56F;org.nutz.dao.entity.annotation.Comment} || +|| {#888;@PrevInsert} ||表或者字段的注释|| @see JDoc: {#56F;org.nutz.dao.entity.annotation.PrevInsert} || +|| {#888;@PrevUpdate} ||表或者字段的注释|| @see JDoc: {#56F;org.nutz.dao.entity.annotation.PrevUpdate} || +|| {#888;@PrevDelete} ||表或者字段的注释|| @see JDoc: {#56F;org.nutz.dao.entity.annotation.PrevDelete} || diff --git a/doc/manual/dao/condition.man b/doc/manual/dao/condition.man index 9ffba4a1a2..5b8f218e1e 100644 --- a/doc/manual/dao/condition.man +++ b/doc/manual/dao/condition.man @@ -88,6 +88,10 @@ Nutz 给你的快速实现 cri.where().andIn("id", 3,4,5).andIn("name", "Peter", "Wendal", "Juqkai"); }else if(...){ cri.where().andLT("id", 9); + }else if(...){ + cri.where().andInBySql("关联字段","select id from 关联表 where name = '%s'",变量); + }else if(...){ + cri.where().andInBySql("关联字段","select id from 关联表 where name like '%%%s%%'",变量); } if(...){ diff --git a/doc/manual/dao/customized_sql.man b/doc/manual/dao/customized_sql.man index 425020f021..552e4bd312 100644 --- a/doc/manual/dao/customized_sql.man +++ b/doc/manual/dao/customized_sql.man @@ -39,7 +39,7 @@ Nutz.Dao 自定义 SQL 的概述 /* delete.data */ DELETE FROM $table WHERE name LIKE @name /* update.data */ - UPDATE FROM $table SET name=@name WHERE id=@id + UPDATE $table SET name=@name WHERE id=@id }}} 在你的 Java 代码中: {{{ @@ -88,6 +88,7 @@ Sql 对象 -- org.nutz.dao.sql.Sql }}} * 在执行 SQL 前,该占位符会被字符 "{#F0F;*?}" 替换,用来创建 PreparedStatement * Nutz.Dao 会自动计算 PreparedStatement的索引值 + * 从1.r.67的2018-12-23号起的快照版,支持带定界符的`${abc}` 和 `@{abc}` 形式 所有的占位符可以同样的名称出现的多个地方。并且变量占位符和参数占位符的名称不互相干扰,比如: {{{ @@ -129,6 +130,10 @@ Sql 对象 -- org.nutz.dao.sql.Sql 这就完了吗?我怎么取得查询的结果呢。是的,同 UPDATE, DELETE, INSERT 不同, SELECT 是需要返回 结果的,但是 Nutz.Dao 也不太清楚怎样为你自定义的 SELECT 语句返回结果,于是,就需要你设置回调。 + Tips: + 如果运行的 sql 语句是类似 "show columns from tableName from dbName" 这样的语句, + 请在调用 dao.execute 方法前调用 sql.forceExecQuery 方法来强制让 nutz 用 select 方式运行该 sql 语句。 + 回调的用处 接上例,你需要这么改造一下你的函数: {{{ @@ -193,27 +198,26 @@ Sql 对象 -- org.nutz.dao.sql.Sql 常用回调 Sqls.callback.XXX 提供了80%以上场景所需要的回调类型,在编写自定义回调之前,建议您先看看有没有现成的 - - | 名称 | 结果类型 | 备注 | - |---------------|---------------|--------------| - |bool | Boolean | | - |bools | boolean[] |1.r.62及之前的版本是LinkedArray| - |doubleValue | Double | | - |entities | `List` |取决于Entity对应的Pojo类 | - |entity | Pojo |取决于Entity对应的Pojo类 | - |floatValue | Float | | - |integer | Integer | | - |ints | int[] | | - |longValue | Long | | - |longs | long[] | | - |map | NutMap |与Record类型,但区分大小写 | - |maps | List | | - |record | Record |字段名均为小写 | - |records | `List`| | - |str | String | | - |strList | List | | - |strs | `String[]` | | - |timestamp | TimeStamp | | + + || 名称 || 结果类型 || 备注 || + ||bool || Boolean || || + ||bools || `boolean[]` ||1.r.62及之前的版本是LinkedArray|| + ||doubleValue || Double || || + ||entities || `List` ||取决于Entity对应的Pojo类 || + ||entity || Pojo ||取决于Entity对应的Pojo类 || + ||floatValue || Float || || + ||integer || Integer || || + ||ints || `int[]` || || + ||longValue || Long || || + ||longs || `long[]` || || + ||map || NutMap ||与Record类型,但区分大小写 || + ||maps || `List` || || + ||record || Record ||字段名均为小写 || + ||records || `List`|| || + ||str || String || || + ||strList || `List` || || + ||strs || `String[]` || || + ||timestamp || TimeStamp || || ---------------------------------------------------------------------------------------------------- Nutz.Dao SQL 文件的格式 @@ -280,7 +284,7 @@ Nutz.Dao SQL 文件的格式 是的,你的 Sql 对象也可以使用 Condition,但是这个 Condition 要如何同你自定义的 SQL 拼装在一起呢, 这里,我提供了一个特殊的变量占位符 -- 条件变量占位符 $condition -------------------------------------------------------------------------------------------- - 特殊的占位符 -- {#00A;*${condition}} + 特殊的占位符 -- {#00A;*$condition} 唯一需要说明的是,在你写作的 SQL 中,需要声明一个特殊的占位符,比如下面的代码输出所有 id 大 于 35 的 Pet 对象的名称 {{{ diff --git a/doc/manual/dao/links_many.man b/doc/manual/dao/links_many.man index 9c1443d27b..baa4a46a1d 100644 --- a/doc/manual/dao/links_many.man +++ b/doc/manual/dao/links_many.man @@ -55,8 +55,8 @@ master.setName("Peter"); List pets = new ArrayList(); - pets.add(new Pet("XiaoBai"); - pets.add(new Pet("XiaoHei"); + pets.add(new Pet("XiaoBai")); + pets.add(new Pet("XiaoHei")); master.setPets(pets); }}} @@ -110,6 +110,15 @@ {{{ Master master = dao.fetchLinks(dao.fetch(Master.class, "Peter"), "pets"); }}} + 或者 + {{{ + Master master = dao.fetchByJoin(Master.class, "pets",Cnd.NEW().and("name","=","Peter")); + }}} + 或者 + {{{ + List masters = dao.queryByJoin(Master.class, "pets",Cnd.NEW().and("name","=","Peter")); + }}} + 然后,你可以通过 master.getPets() 得到 Nutz.Dao 为 master.pets 字段设置的值。 ------------------------------------------------------------------------------------------------------------- diff --git a/doc/manual/dao/links_many_many.man b/doc/manual/dao/links_many_many.man index 319cc9543b..ff79306e13 100644 --- a/doc/manual/dao/links_many_many.man +++ b/doc/manual/dao/links_many_many.man @@ -225,6 +225,10 @@ NutDao 是如何连接关联表的 {{{ Food food = dao.fetchLinks(dao.fetch(Food.class, "Fish"), "pets"); }}} + 或者 + {{{ + List foodList = dao.queryByJoin(Food.class, "pets",Cnd.NEW().and("name","=","Fish")); + }}} 然后,你可以通过 food.getPets() 得到 Nutz.Dao 为 food.pets 字段设置的值。 ------------------------------------------------------------------------------------------------------------- diff --git a/doc/manual/dao/next_prev.man b/doc/manual/dao/next_prev.man index b4403a139a..9f5c4111af 100644 --- a/doc/manual/dao/next_prev.man +++ b/doc/manual/dao/next_prev.man @@ -54,6 +54,26 @@ @Next 的规则和 @Prev 是一样的 ----------------------------------------------------------------------------------------------------------------- +拦截类注解 - @PrevInsert/@PrevUpdate@PrevDelete + + 与@Prev/@Next不同, 这几个注解不涉及SQL操作,这是最大的区别! + + 因为不涉及到sql操作, 所以不影响批量操作,尤其是批量插入. + + 先举个例子,生成uuid,说一下两者的区别: + {{{ + @Name + @Prev(els={@EL("uuid()")} // 使用dao.fastInsert(list)会出现name为null,因为@Prev不执行 + @PrevInsert(@EL("uuid()"} // 使用dao.fastInsert(list)依然正常,@PrevInsert会执行 + private String name; // 一个字段上不要同时写@Prev和@PrevInsert,上述示例只是为了说明功能 + + @PrevUpdate(now=true) // 执行update时,自动设置为当前时间 + private Date updateTime; + }}} + + 上述例子中,@PrevInsert可完美替代@Prev, 实现批量插入,而且可以简写为@PrevInsert(uu32=true) + +----------------------------------------------------------------------------------------------------------------- 以 @Prev 来举例 下面让我们举两个例子,详细说明一下 {*变量} 和 {*参数} 的异同点。 diff --git a/doc/manual/dao/pojo_interceptor.man b/doc/manual/dao/pojo_interceptor.man new file mode 100644 index 0000000000..7a4ee54166 --- /dev/null +++ b/doc/manual/dao/pojo_interceptor.man @@ -0,0 +1,57 @@ +#title: Pojo拦截器 +#index:0,1 +--------------------------------------------------------------------------------------------------- +PojoInterceptor能解决什么问题 + + * 自定义主键生成策略 + * 更新对象时自动更新时间戳 + * 删除对象时自动清除缓存 + * 其他自定义Pojo行为 + +--------------------------------------------------------------------------------------------------- +默认实现类DefaultPojoInterceptor + + 拦截器类型是通过@Table(interceptor=XXX.class)配置的,默认是DefaultPojoInterceptor + + DefaultPojoInterceptor当前实现了3个注解, @PrevInsert/@PrevUpdate/@PrevDelete, 具体用法请查阅[next_prev.man 插入前后的设置] + +---------------------------------------------------------------------------------------------------- +当前支持的事件及其触发条件 + + * prevInsert 执行dao.insert/dao.fastInsert + * prevUpdate 执行dao.update + * prevDelete 执行dao.delete + +---------------------------------------------------------------------------------------------------- +扩展示例 + + 自定义更新行为 @MyUpdateTime + + {{{ + public MyPojoInterceptor extends DefaultPojoInterceptor { + + protected void setupFieldAnnotation(final MappingField mf, final Field field, final Annotation anno) { + super.setupFieldAnnotation(mf,field,anno); + if (anno instanceof MyUpdateTime) { + super.list.add(new BasicPojoInterceptor() { + public void onEvent(Object obj, Entity en, String event, Object... args) { + if ("prevUpdate".equals(event) { + // 对obj进行操作 + // MappingField有字段信息 + // Field的java属性反射对象 + } + } + }); + } + } + } + }}} + +------------------------------------------------------------------------------------------------------- +禁用该特性 + + {{{ + static { + NutConf.DAO_USE_POJO_INTERCEPTOR = false; + } + }}} \ No newline at end of file diff --git a/doc/manual/dao/update_with_version.man b/doc/manual/dao/update_with_version.man index 31597371f4..1a5dd78beb 100644 --- a/doc/manual/dao/update_with_version.man +++ b/doc/manual/dao/update_with_version.man @@ -23,7 +23,7 @@ Nutz实现方式 * 只支持实体类形式的更新,包括实体类集合 * 乐观锁更新方法 dao.updateWithVersion * 调用dao.updateWithVersion后,version字段值自动加1 - * 设置为version字段后,dao.insert默认赋值0 + 代码片段 @@ -33,7 +33,7 @@ Nutz实现方式 private String name; @Column private int age; - @Column(value="version") + @Column(version = true) private int version; } @@ -41,4 +41,4 @@ Nutz实现方式 dao.updateWithVersion(pet); // 实际执行的SQL update table set age=?,version=version+1 where name=? and version=? - }}} \ No newline at end of file + }}} diff --git a/doc/manual/history/1_r_63.man b/doc/manual/history/1_r_63.man new file mode 100644 index 0000000000..a99605901c --- /dev/null +++ b/doc/manual/history/1_r_63.man @@ -0,0 +1,42 @@ +#title: 1.r.63 发行注记 +#index:0,1 +#author:wendal(wendal1985@gmail.com) +-------------------------------------------------------------------------------------------------------- +1.r.63 {*香梨} 发行注记(20171116) + + 4个月前,我们打算发布一个8周年纪念版. 结果,连双十一都过完了... + + 8年前,那时候还叫"javaeye"的新闻栏目,登出了一条不太平凡的小新闻, [http://www.iteye.com/news/10461 Nutz框架 1.a.15发布了]. + + 那时候,还用着google code,然而现在是github和码云的天下. + + 那时候还出过"四不像","斗鱼", "guzz"等很多框架,能坚持到现在还更新的只剩下很少一部分. + + 那时候,有人说nutz停更了怎么办?收费了怎么办?闭源了怎么办? 嗯,49个版本,8年持续改进,免费且完整开源,永久的,我们说到做到. + + 现在,我们毫不夸张地说,用nutz做的项目能绕地球一圈 + + 顺带说一下, NB项目(NutzBoot的简称)已经2.0-Preview了,来[https://nutz.io NB官网]逛逛吧 + +--------------------------------------------------------------------------------------------------------- +主要变化: + + 啊啊啊,共40多个issue和pull request,自己去看吧 [https://github.com/nutzam/nutz/milestone/30?closed=1 milestone 1.r.63] + + 然而,最大的变化是, 我们又新增了一位commiter [happyday517 https://github.com/happyday517] + +-------------------------------------------------------------------------------------------------------- +文档更新 + + * Images的新功能 + * NB的文档(持续更新中) + * 各种新功能的补充性文档 + +-------------------------------------------------------------------------------------------------------- +详细列表: + + * [https://github.com/nutzam/nutz/issues?q=is%3Aissue+is%3Aclosed+milestone%3A1.r.63 issue@github] + +欢迎访问[https://nutzam.com 官网] 及 [https://nutz.cn Nutz社区],以获取更多信息 + +[https://nutz.cn Nutz社区]已经累计了6000多帖子, 30000+条回复,平均回复时间少于10分钟哦,白天基本上秒回! diff --git a/doc/manual/history/1_r_65.man b/doc/manual/history/1_r_65.man new file mode 100644 index 0000000000..b6d6b43062 --- /dev/null +++ b/doc/manual/history/1_r_65.man @@ -0,0 +1,64 @@ +#title: 1.r.65 发行注记 +#index: 0,1 +#author: 胖五(pangwu86@gmail.com) + +-------------------------------------------------------------------------------------------------------- +1.r.65 {*怪物猎人} 发行注记(20180128) + + <../imgs/1r65.png> + + 2018已经过了快1个月,各位同学的年度总结是不是也写好了。 + + 回顾2017,来看看Nutz都做了哪些事情: + * Nutz核心包发布了共4个版本,名字都是某位广东人喜欢的水果 + * NutzBoot项目立项且发布,直接窜上2.0 + * NutzCloud项目立项且发布,没错就在NB的2.1版本中 + * Nutz官网更新了一版,满足了PC与手机端访问 + + + 总的来说相比前两年还是做了一些新东西出来,当然这也包括了一些尚未公开的项目。 + + 就在一周前,Nutz核心组的几名成员相聚长沙黄兴路步行街的金拱门餐厅,在一边吃薯条一边喝可乐的愉悦氛围下定下了2018年的目标,可以告诉大家的是“今年会有很多有趣的事情”要发生,至于具体内容将在春节前后给出答案。总的来说我们希望Nutz越来越有范,除了代码写的好其他方面也要跟上时代进步。 + + 就在本周PS4游戏《怪物猎人 世界》正式发售了,伴着勇气之证的BGM,猎人们再次集结起来加入狩猎古龙。 + + 很喜欢这种多人组队做任务的设定,所以也希望Nutz社区在今年变得更加有趣,让更多的Nutz猎人加入进来,跟我们一起来狩猎2018。 + + +--------------------------------------------------------------------------------------------------------- +主要变化 + + 距离上次发布仅一个月,内容主要是小Feature和Bug修改,请放心升级 + * add: 坐标点旋转计算方法 + * add: Ioc接口添加addBean方法 + * add: 增加web环境下 国际化 相关帮助函数 + * add: Mvcs增加辅助函数直接取得国际化信息配合NutzCodeInsight实现国际化配置代码折叠提示 + * add: 通过Daos辅助函数自动创建表时,对不需要自动创建得表进行过滤的功能 + * add: Times.d2TS(Date日期转Unix时间戳) + * add: 添加两个老的scanModuleInPackage和isModule方法,兼容老代码 + * add: Aop类与NutIoc容器一对一绑定的功能,但默认禁用 + * add: 根据类上的注解获取ioc对象的name列表 + * add: CrossOriginFilter添加X-Requested-With,与jetty的CrossOriginFilter一致 + * fix: countByJoin没做对 + * fix: Json.fromJson 处理date类型时区的问题 + * fix: queryByJoin要进行分页查询的时候dao.count没有关联查询的方法 + * fix: Column不支持@Index + * fix: boot文档里面有链接错误 + * fix: map.entrySet() 得到的对象无法 Json.toJson + * fix: 建表的时候, 如果某个类报错, 应该继续建其他类,最后再抛出异常 + * fix: JsonAopConfigrationTest失败 + * fix: 为NutTxDao添加个testcase + * fix: 容忍非法转义,可配置 + * fix: AndOpt和OrOpt,修改强制类型转换时,没有考虑右值的问题 + * fix: Jdbcs.guess方法有NPE的可能性 + * fix: https://gitee.com/nutz/nutz/issues/IHHHK + + + 还有就是发行注记改由胖五负责,并配上题图 + +-------------------------------------------------------------------------------------------------------- +详细列表 + + * [https://github.com/nutzam/nutz/issues?q=is%3Aissue+is%3Aclosed+milestone%3A1.r.65 Issue@github] + * 欢迎访问[https://nutzam.com 官网] 及 [https://nutz.cn Nutz社区],以获取更多信息 + * [https://nutz.cn Nutz社区]已经累计了6000多帖子, 30000+条回复,平均回复时间少于10分钟哦,白天基本上秒回! diff --git a/doc/manual/history/1_r_66.man b/doc/manual/history/1_r_66.man new file mode 100644 index 0000000000..fe76051c62 --- /dev/null +++ b/doc/manual/history/1_r_66.man @@ -0,0 +1,69 @@ +#title: 1.r.66 发行注记 +#index: 0,1 +#author: 胖五(pangwu86@gmail.com) + +-------------------------------------------------------------------------------------------------------- +1.r.66 {*风花雪月} 发行注记(20180615) + + + <../imgs/1r66/1r66.png> + + 相隔了快5个月后,我们又回来了。 + + 首先吐槽下上次发布版本后定的一堆目标,到目前为止一个都没实现... + + 定好了4月来点动作,之后很自然的推迟到5月,又从5月很自然推迟到6月,然后6月已经过半了,还没影呢... + + <../imgs/1r66/shuosha.gif> + + 不过好在NutzBoot一直在wendal的带领下有条不紊的发着版本,非常牛逼非常给力!据说某平台的star数也已经超过了nutz... + +-------------------------------------------------------------------------------------------------------- +关于接下来会做什么 + + 实际上我们准备的第一个动作是开个定期的直播活动,也偷偷的试播了2次 + + 准备了背景板也确定好了话题,然后就没有了下文,月播的计划也沦为季播甚至可能变成年播/(ㄒoㄒ)/~~ + + <../imgs/1r66/live.png> + + 总之我们痛定思痛,认为这个事情还是要提上日程,希望能在下个月(或下下个月)的某一天与你们见面吧 + + 当然还有官网与论坛的改版,已经在做了。只是目前做的东西还是属于内部开发阶段,不能公开,具体的进度也等着直播的时候跟大家好好聊聊。 + + 希望我们渡过这段非常时期后能恢复到日常较缓慢的节奏中,留出更多的精力去做些有趣的事情。 + + +-------------------------------------------------------------------------------------------------------- +题图注释 + + 老任的E3上发表了《火焰纹章》的新作,日版副标题是”风花雪夜“ 这起的可是非常的中国呀,要知道美版标题可是“Three Houses”(三间屋).... + + 总之这个好听的名字一下子打动了me,就顺利的成了这次的版本代号。 + +--------------------------------------------------------------------------------------------------------- +主要变化 + + 内容主要是小Feature和Bug修改,请放心升级 + * add: 缓存正则表达式 + * add: 统一考虑一下JDK8的LocalDate相关的支持 + * fix: NutDao.fastInsert字段解析不全 + * fix: dao.count()无法解析自定义sql拼接后的SimpleCriteria + * fix: Json.toJson 方法传入 Timestamp 类型时输出精度不正确的问题 + * fix: UploadAdaptor,服务每次启动总是会生成一个临时文件 + * fix: postgresql 获取 blob得到null + * fix: Mirror getAnnotation NPE + * fix: Mirror#findMethod 在抽象类中静态方法查找报错 + * fix: NutzDao insertOrUpdate(t) @Id的字段 id为包装器类型报错 + * fix: org.nutz.lang.Stopwatch 秒表意见 + * fix: DaoUpTest注释写错了吧 + * fix: 存在不同ioc不同classloader 相同 bean name 被错误缓存的bug + * fix: Nutz json 支持 final 字段 + * fix: Json: 支持JDK8的LocalDate和LocalDateTime + +-------------------------------------------------------------------------------------------------------- +详细列表 + + * [https://github.com/nutzam/nutz/issues?q=is%3Aissue+is%3Aclosed+milestone%3A1.r.66 Issue@github] + * 欢迎访问[https://nutzam.com 官网] 及 [https://nutz.cn Nutz社区],以获取更多信息 + * [https://nutz.cn Nutz社区]已经累计了6000多帖子, 30000+条回复,平均回复时间少于10分钟哦,白天基本上秒回! diff --git a/doc/manual/imgs/1r65.png b/doc/manual/imgs/1r65.png new file mode 100644 index 0000000000..ec6c77a8e2 Binary files /dev/null and b/doc/manual/imgs/1r65.png differ diff --git a/doc/manual/imgs/1r66/1r66.png b/doc/manual/imgs/1r66/1r66.png new file mode 100644 index 0000000000..d4e870dffd Binary files /dev/null and b/doc/manual/imgs/1r66/1r66.png differ diff --git a/doc/manual/imgs/1r66/live.png b/doc/manual/imgs/1r66/live.png new file mode 100644 index 0000000000..4f19b6ec4f Binary files /dev/null and b/doc/manual/imgs/1r66/live.png differ diff --git a/doc/manual/imgs/1r66/shuosha.gif b/doc/manual/imgs/1r66/shuosha.gif new file mode 100644 index 0000000000..06336f771a Binary files /dev/null and b/doc/manual/imgs/1r66/shuosha.gif differ diff --git a/doc/manual/index.xml b/doc/manual/index.xml index 603a1d7035..57e0cf13b6 100644 --- a/doc/manual/index.xml +++ b/doc/manual/index.xml @@ -65,6 +65,7 @@ + @@ -142,6 +143,11 @@ + + + + + @@ -155,6 +161,9 @@ + + + diff --git a/doc/manual/integration/freemarker.man b/doc/manual/integration/freemarker.man index 9386d5f153..0a58692d5e 100644 --- a/doc/manual/integration/freemarker.man +++ b/doc/manual/integration/freemarker.man @@ -13,7 +13,7 @@ org.nutz nutz-plugins-views - 1.r.60 + 1.r.65 }}} @@ -23,7 +23,7 @@ 修改MainModule类,添加引用 {{{ - @Views(value={FreeMarkerViewMaker.class}) + @Views(value={FreemarkerViewMaker.class}) // 其他配置,如IocBy等等 public class MainModule {} }}} diff --git a/doc/manual/ioc/injecting.man b/doc/manual/ioc/injecting.man index 207d59a7e4..dd2e306b01 100644 --- a/doc/manual/ioc/injecting.man +++ b/doc/manual/ioc/injecting.man @@ -147,6 +147,7 @@ } }}} `{file : '文件路径'}` 可以是绝对路径,也可以是 CLASSPATH 中的路径 + 注意: 如果是CLASSPATH路径,这个文件就不能打包进一个jar文件里面 数组或容器 如果你对象某个字段是数组,集合,或者 Map, 用 JSON 可以很自然为其设置值,不是吗? diff --git a/doc/manual/ioc/loader_annotation.man b/doc/manual/ioc/loader_annotation.man index a537efd619..d743a2000d 100644 --- a/doc/manual/ioc/loader_annotation.man +++ b/doc/manual/ioc/loader_annotation.man @@ -214,8 +214,19 @@ 但是,如果我想注入的容器对象同超类的那个字段名字不一样怎么办? 或者我不打算注入一个容器对象,我想注入一个 字符串,或者布尔值怎么办? 呵呵,抱歉,截止到现在,我们还没有解决这个问题,不过很快我们会给出一个设计, 并且我可以负责的说,给出新的设计一定会兼容现在的这个用法的。 + + 如果注入的超类字段是optional的怎么办 + + 答 + {{{ + @IocBean(fields={"dao!optional"}) + public AbcService extends Service { + ... + }}} + + 正如你所见到的,只需要在注入的对象后增加"!optional"即可,是不是很简单^_^ ------------------------------------------------------------------------------------------------------- 工厂方法 - 工厂方法已经移入独立的章节 \ No newline at end of file + 工厂方法已经移入独立的章节 diff --git a/doc/manual/json/mvc.man b/doc/manual/json/mvc.man index b9749db31f..d491bbd818 100644 --- a/doc/manual/json/mvc.man +++ b/doc/manual/json/mvc.man @@ -37,10 +37,26 @@ Mvc中使用Json 内置的模式,一对一对应JsonFormat中的几个快捷方法 {{{ + /** + * 一般模式 -- 换行,但忽略null值 + */ @Ok("json:nice") + /** + * 全部输出模式 -- 换行,不忽略null值 + */ @Ok("json:full") + /** + * 为了打印出来容易看,把名字去掉引号 + */ @Ok("json:forLook") + /** + * 紧凑模式 -- 无换行,忽略null值 + */ @Ok("json:compact") + /** + * 不换行,不忽略空值 + */ + @Ok("json:tidy") }}} 详细配置, 后面的就是JsonFormat的json形式而已,与JsonFormat的属性一一对应 diff --git a/doc/manual/json/to.man b/doc/manual/json/to.man index 0944a5826a..54265480a9 100644 --- a/doc/manual/json/to.man +++ b/doc/manual/json/to.man @@ -75,7 +75,7 @@ JsonFormat详解 Json.toJson(pet, JsonFormat.full().setQuoteName(false)); // 输出类似 {name:"wendal"} }}} - ignoreNull -- 是否忽略控制,默认为false + ignoreNull -- 控制是否忽略null值,默认为false {{{ Json.toJson(pet, JsonFormat.full().setIgnoreNull(true)); // null值的key-value将不会输出,但空List还是会的. diff --git a/doc/manual/lang/lang.man b/doc/manual/lang/lang.man index 48263ae807..d6139683db 100644 --- a/doc/manual/lang/lang.man +++ b/doc/manual/lang/lang.man @@ -11,7 +11,7 @@ 可以做很多事情, 而且比“甜甜”的 Ruby 也差不了太多。 你可以查看 [https://github.com/nutzam/nutz/tree/master/src/org/nutz/lang org.nutz.lang] [https://git.oschina.net/nutz/nutz/tree/master/src/org/nutz/lang Git@OSC镜像] 下 - 的源代码。为了便于你学习,我将里面部分最常用的用法列在文档你,便于快速学习查看。 + 的源代码。为了便于你学习,我将里面部分最常用的用法列在文档里,便于快速学习查看。 我希望在 80% 以上的情况下,这些函数能让你有效的缩短你代码的体积,并且增加代码的可读性。 diff --git a/doc/manual/maplist/overview.man b/doc/manual/maplist/overview.man index f1fe23b0e7..c47f729b6d 100644 --- a/doc/manual/maplist/overview.man +++ b/doc/manual/maplist/overview.man @@ -143,7 +143,7 @@ Mapl.转 对象 * list的访问使用 "名称`[索引]`", 如: `as[1]`. 当然要是不想写`[]`也可以使用 as.1.name的形式. * 顶层为list时, 使用 "`[索引].其它`", 如: `[1].name` * 如果想得到一个List, 而不是它某个值, 则可以使用 "名称" 不加 "`[索引]`". 如: as - * 如果List后加了"`[]`"中间却没有索引, 则默认访问第一个元素, 如: `user[]` 等效 `user[1]` + * 如果List后加了"`[]`"中间却没有索引, 则默认访问第一个元素, 如: `user[]` 等效 `user[0]` ------------------------------------------------------------------------ maplist 合并 @@ -269,7 +269,7 @@ maplist 结构转换 * 有数组的, 只写第一个元素的结构 * 原结构中的值, 以字符串或字符串数组做为目标结构的对应关系 * 对应关系可以为数组 - * 有数组的, 目标结构以key[].abc来代替数组 + * 有数组的, 目标结构以key`[]`.abc来代替数组 * 原结构数组层次强制限定一致, 目标结构中'`[]`'的索引按原结构中出现先后顺序进行匹配. * 如果原结果不存在, 那默认为0 * 未在模板中申明的不做转换 diff --git a/doc/manual/mvc/file_upload.man b/doc/manual/mvc/file_upload.man index 308da73d1e..3f938acb72 100644 --- a/doc/manual/mvc/file_upload.man +++ b/doc/manual/mvc/file_upload.man @@ -195,21 +195,55 @@ 是的,它们会被变成一个数组。顺序同 `
` 中的顺序。 但是这里,有一点{#F00;*限制} - * {#F00;* 你只能用 `TempFile[]`} + * {#F00;* 你只能用 TempFile`[`]} 也就是说,如果你用了 `File[]` 将会出错。 ------------------------------------------------------------------------------------- 可用的参数类型 - * TempFile 及 `TempFile[]` + * TempFile 及 TempFile`[`] * File 推荐用TempFile * Map 用于得到全部参数 ------------------------------------------------------------------------------------- +AJAX 方式上传文件 + + 现在的浏览器配合 jQuery 的话是很方便上传文件的,例子如下,省略 html 代码 + + {{{ + + }}} + + {{{ + @At("/upload") + @AdaptBy(type = UploadAdaptor.class, args = {"${app.root}/WEB-INF/tmp"}) + public void uploadPhoto(@Param("file") File f) { + System.out.println(f.getName()); + } + }}} + + 其实这就是在画面生成一个 form 对象,然后用 AJAX POST 方式来提交这个 form。是不是一下让你想到远古时期用 js 提交 form 表单的做法呢。 +------------------------------------------------------------------------------------- 推荐的js插件 - ajax下是不能直接提交带文件的表单的,但良好的用户体验,怎么能不使用ajax上传文件呢? - * [http://fex.baidu.com/webuploader/ webuploader] 百毒出品,体验还是不错的 * [https://github.com/codler/jQuery-Ajax-Upload jQuery-Ajax-Upload] 老牌ajax上传插件 diff --git a/doc/manual/mvc/hello-idea.man b/doc/manual/mvc/hello-idea.man index a5d792b9b5..0f0d0cf49a 100644 --- a/doc/manual/mvc/hello-idea.man +++ b/doc/manual/mvc/hello-idea.man @@ -142,6 +142,7 @@ 仅仅是输出入口函数的返回值。是的,无论入口函数返回什么,都会保存在 request 对象 "obj" 属性中 启动服务 点击右侧Maven Projects > Plugins > jetty > jetty:run + (如果maven面板Plugins内没有jetty,尝试删除pom文件中标签) 控制台输出 diff --git a/doc/manual/mvc/http_adaptor.man b/doc/manual/mvc/http_adaptor.man index 502eede097..9bca95a9bc 100644 --- a/doc/manual/mvc/http_adaptor.man +++ b/doc/manual/mvc/http_adaptor.man @@ -227,6 +227,7 @@ "data": JSON.stringify(data), // 注意要转为json,除非data本身就是json字符串 dataType:'json', type : 'POST', + contentType: 'application/json', // 推荐添加 success:function(re){ console.log(re); } diff --git a/doc/manual/mvc/localization.man b/doc/manual/mvc/localization.man index 4a300328d2..e7027ec209 100644 --- a/doc/manual/mvc/localization.man +++ b/doc/manual/mvc/localization.man @@ -1,165 +1,165 @@ -#title: 本地化字符串 -#index:0,1 -#author: zozoh(zozohtnt@gmail.com) ------------------------------------------------------------------------------------------- -基本策略 - 每个 Mvc 框架都有自己的本地化字符串的解决方案, Nutz.Mvc 的这个是相当简陋的。 - 我只是个人觉得足够用了。下面我把它简单介绍一下: - - * 假定所有的本地化字符串文件都会存放在某一{* 目录}下 - * 这个目录下所有的 .properties 文件,将作为默认的本地字符串文件。 - * 每一种语言都会是一个目录,目录名称对应一个 Locale 的 toString(),请参看 java.util.Locale 的 JDoc - * 比如简体中文,就是 zh_CN - * 比如美式英语,就是 en_US - * 目录下所有的 .properties 文件存放着该地区的字符串信息 - * .properties 文件需要按照 UTF-8 方式编码 - * 目录,通过 @Localization("路径/") 声明在主模块上 - * 当应用启动时,一次读入所有的字符串,并存入 ServletContext,属性名称为:“org.nutz.mvc.annotation.Localization” - * 应用可以自行设置当前 Session 是哪一个国家和地区 - * Mvcs.setLocaleName(String localeName) - * 每次请求时,会根据 Session 中的 localeName,从 ServletContext 中将对应 Locale 的字符串取出,设入 Request 对象 - * 属性名为 "msg",无论@Localization声明的路径是什么,在页面(JSP/JSTL/其他模板引擎)中, 它的名字都不会变化. - * 如果当前会话没有被设置 Locale,则将 "msg" 设置成默认本地化字符串 ------------------------------------------------------------------------------------------- -使用字符串 - - 目录结构, 以maven为例 - - {{{ - - src - - main - - resources - - msgs - - zh_CN - user.properties - role.properties - - en_US - user.properties - role.properties - }}} - - 在主模块上声明 - 比如: - {{{ - ... - @Localization("msgs/") // 注意是文件夹的名字 - public class MainModule { - ... - }}} - * 在主模块上声明 `@Localization` 注解,指向一个目录 - * 在目录下建立文件夹,比如 {*zh_CN},每个目录下所有 {* .properties} 文件都会被当作字符串文件 - * {* .properties} 文件 一定要是 UTF-8 编码的 - * 比如 `@Locallization("msgs/")` 会指向 CLASSPATH 下的 {*msgs/} 目录 - - 在 JSP 里使用 - {*直接 Scriptlet} - {{{ - ... -

<%=((Map)request.getAttribute("msg")).get("my.msg.key")%>

- ... - }}} - - {*采用 JSTL} - {{{ - ... -

${msg['my.msg.key']}

- ... - }}} - - 我到底支持哪些语言 - - 请参看 {*org.nutz.mvc.Mvcs} 的 JavaDoc,这里我列几个常用的方法: - - || Mvcs.getLocalizationKey() || 获取当前会话的 Locale 名称 || - || Mvcs.setLocalizationKey(String key) || 为当前会话设置 Locale 的名称 || - || Mvcs.getLocalizationKeySet() || 获取整个应用可用的 Locale 名称集合 || - ------------------------------------------------------------------------------------------- -切换本地语言 - - {{{ - // 设置一个本地字符串 - @At("/lang/change") - @Ok("redirect:/") - public void changeLocal( @Param("lang") String lang){ - Mvcs.setLocalizationKey(lang); - } - }}} - ------------------------------------------------------------------------------------------- -设置应用程序的默认语言 - - {{{ - ... - @Localization(value="mymsg/", defaultLocalizationKey="zh_CN") - public class MainModule { - ... - }}} - ------------------------------------------------------------------------------------------- -定制自己的本地化字符串方式 - - 你需要自己实现一个 MessageLoader 的接口,然后声明到 '@Localization' 中。 - 比如你的实现类名字为 'MyMessageLoader',那么你应该这么声明: - {{{ - ... - @Localization( type=MyMessageLoader.class, - value="msg" ) - public class MainModule { - ... - }}} - - 对于 MessageLoader 接口,就一个方法需要你来实现: - {{{ - public interface MessageLoader { - /** - * 本函数将根据传入的 "refer" 参数,返回一个 Map
- * Map 的键是语言的名称,比如 "en_US", "zh_CN" 等,
- * 你会通过 Mvcs.setLocalizationKey 来直接使用这个键值 - *

- * 与键对应的是一个消息字符串的 Map, 该 Map 的键就是消息键,值就是消息内容 - * - * @param refer - * 参考值。来自 '@Localization.value' - * @return 多国语言字符串的 Map - */ - Map> load(String refer); - } - }}} - - 你声明在 '@Localization' 中的 "value" 的值,会被传入这个接口,作为 refer 参数的值 - ------------------------------------------------------------------------------------------- -让 Ioc 容器管理你的 MessageLoader - - 通过beanName进行设置: - {{{ - ... - @Localization( type=MessageLoader.class, - beanName="myMessages", - value="msg" ) - public class MainModule { - ... - }}} - - 提供了 "beanName" 属性,这样,Nutz.Mvc 将从 Ioc 容器中加载名字为 "myMessages" 的对象。 - 当然它的类型实现了 "MessageLoader" 接口 - - - - - - - - - - - - - - - - - - - +#title: 本地化字符串 +#index:0,1 +#author: zozoh(zozohtnt@gmail.com) +------------------------------------------------------------------------------------------ +基本策略 + 每个 Mvc 框架都有自己的本地化字符串的解决方案, Nutz.Mvc 的这个是相当简陋的。 + 我只是个人觉得足够用了。下面我把它简单介绍一下: + + * 假定所有的本地化字符串文件都会存放在某一{* 目录}下 + * 这个目录下所有的 .properties 文件,将作为默认的本地字符串文件。 + * 每一种语言都会是一个目录,目录名称对应一个 Locale 的 toString(),请参看 java.util.Locale 的 JDoc + * 比如简体中文,就是 zh_CN + * 比如美式英语,就是 en_US + * 目录下所有的 .properties 文件存放着该地区的字符串信息 + * .properties 文件需要按照 UTF-8 方式编码 + * 目录,通过 @Localization("路径/") 声明在主模块上 + * 当应用启动时,一次读入所有的字符串,并存入 ServletContext,属性名称为:“org.nutz.mvc.annotation.Localization” + * 应用可以自行设置当前 Session 是哪一个国家和地区 + * Mvcs.setLocalizationKey(String localeName) + * 每次请求时,会根据 Session 中的 localeName,从 ServletContext 中将对应 Locale 的字符串取出,设入 Request 对象 + * 属性名为 "msg",无论@Localization声明的路径是什么,在页面(JSP/JSTL/其他模板引擎)中, 它的名字都不会变化. + * 如果当前会话没有被设置 Locale,则将 "msg" 设置成默认本地化字符串 +------------------------------------------------------------------------------------------ +使用字符串 + + 目录结构, 以maven为例 + + {{{ + - src + - main + - resources + - msgs + - zh_CN + user.properties + role.properties + - en_US + user.properties + role.properties + }}} + + 在主模块上声明 + 比如: + {{{ + ... + @Localization("msgs/") // 注意是文件夹的名字 + public class MainModule { + ... + }}} + * 在主模块上声明 `@Localization` 注解,指向一个目录 + * 在目录下建立文件夹,比如 {*zh_CN},每个目录下所有 {* .properties} 文件都会被当作字符串文件 + * {* .properties} 文件 一定要是 UTF-8 编码的 + * 比如 `@Locallization("msgs/")` 会指向 CLASSPATH 下的 {*msgs/} 目录 + + 在 JSP 里使用 + {*直接 Scriptlet} + {{{ + ... +

<%=((Map)request.getAttribute("msg")).get("my.msg.key")%>

+ ... + }}} + + {*采用 JSTL} + {{{ + ... +

${msg['my.msg.key']}

+ ... + }}} + + 我到底支持哪些语言 + + 请参看 {*org.nutz.mvc.Mvcs} 的 JavaDoc,这里我列几个常用的方法: + + || Mvcs.getLocalizationKey() || 获取当前会话的 Locale 名称 || + || Mvcs.setLocalizationKey(String key) || 为当前会话设置 Locale 的名称 || + || Mvcs.getLocalizationKeySet() || 获取整个应用可用的 Locale 名称集合 || + +------------------------------------------------------------------------------------------ +切换本地语言 + + {{{ + // 设置一个本地字符串 + @At("/lang/change") + @Ok("redirect:/") + public void changeLocal( @Param("lang") String lang){ + Mvcs.setLocalizationKey(lang); + } + }}} + +------------------------------------------------------------------------------------------ +设置应用程序的默认语言 + + {{{ + ... + @Localization(value="mymsg/", defaultLocalizationKey="zh_CN") + public class MainModule { + ... + }}} + +------------------------------------------------------------------------------------------ +定制自己的本地化字符串方式 + + 你需要自己实现一个 MessageLoader 的接口,然后声明到 '@Localization' 中。 + 比如你的实现类名字为 'MyMessageLoader',那么你应该这么声明: + {{{ + ... + @Localization( type=MyMessageLoader.class, + value="msg" ) + public class MainModule { + ... + }}} + + 对于 MessageLoader 接口,就一个方法需要你来实现: + {{{ + public interface MessageLoader { + /** + * 本函数将根据传入的 "refer" 参数,返回一个 Map
+ * Map 的键是语言的名称,比如 "en_US", "zh_CN" 等,
+ * 你会通过 Mvcs.setLocalizationKey 来直接使用这个键值 + *

+ * 与键对应的是一个消息字符串的 Map, 该 Map 的键就是消息键,值就是消息内容 + * + * @param refer + * 参考值。来自 '@Localization.value' + * @return 多国语言字符串的 Map + */ + Map> load(String refer); + } + }}} + + 你声明在 '@Localization' 中的 "value" 的值,会被传入这个接口,作为 refer 参数的值 + +------------------------------------------------------------------------------------------ +让 Ioc 容器管理你的 MessageLoader + + 通过beanName进行设置: + {{{ + ... + @Localization( type=MessageLoader.class, + beanName="myMessages", + value="msg" ) + public class MainModule { + ... + }}} + + 提供了 "beanName" 属性,这样,Nutz.Mvc 将从 Ioc 容器中加载名字为 "myMessages" 的对象。 + 当然它的类型实现了 "MessageLoader" 接口 + + + + + + + + + + + + + + + + + + + diff --git a/doc/manual/mvc/rest.man b/doc/manual/mvc/rest.man index 55d54bb2ca..c4ef9ec318 100644 --- a/doc/manual/mvc/rest.man +++ b/doc/manual/mvc/rest.man @@ -16,11 +16,13 @@ -------------------------------------------------------------------------------------- 如何使用 REST - Nutz.Mvc 对于 REST 的支持,包括4个常用方法及通用定义方法: + Nutz.Mvc 对于 REST 的支持,包括6个常用方法及通用定义方法: * GET * POST * PUT * DELETE + * OPTIONS + * PATCH * @At(methods="xxx,yyy,zzz") 比如,你有一个 User 对象,你想为其增加 "修改" 和 "删除" 的操作,在模块类里你可以定义如下两个方法 @@ -42,7 +44,7 @@ public void deleteUser(int userId){ // TODO 这里是实现代码 } - + // 任意自选方法 @At(value="/user/?", method="fuck") public void fuckUser(int userId){ @@ -58,21 +60,23 @@ 关于 "路径参数" 的具体的解释,请参看 [http_adaptor.man 适配器] 在一个入口函数上,你可以标注一个或多个下面的注解: - * {#888888;* `@GET`} - * {#888888;* `@POST`} - * {#888888;* `@PUT`} - * {#888888;* `@DELETE`} + * {#888888; @GET} + * {#888888; @POST} + * {#888888; @PUT} + * {#888888; @DELETE} + * {#888888; @OPTIONS} + * {#888888; @PATCH} 这几个注解描述了当前入口函数仅仅相应什么样的 HTTP 请求方法。在你的应用运行时,即使 Nut.Mvc 匹配上了 URL,如果 HTTP 请求的方法和你声明的不符,它也当这个入口函数不存在。 - 如果你不声明这四个注解中的任何一个,则表示你希望你的这个入口函数处理发送到这个 URL 的任何 + 如果你不声明这六个注解中的任何一个,则表示你希望你的这个入口函数处理发送到这个 URL 的任何 HTTP 请求。 - 因此,你可以为一个入口函数声明 (@GET|@POST|@PUT|@DELETE) 中的一个或多个,处理其中一种或者多种 + 因此,你可以为一个入口函数声明 (@GET|@POST|@PUT|@DELETE|@OPTIONS|@PATCH) 中的一个或多个,处理其中一种或者多种 HTTP 请求,而另外一个入口函数不声明注解,用来处理余下的其他 HTTP 请求方法。当然,这两个入口 函数的 @At 应该是一致的。 - @since 1.r.63,标注了上述4个注解的方法,可以省略@At,此时,相当于标注了一个默认的@At + @since 1.r.63,标注了上述6个注解的方法,可以省略@At,此时,相当于标注了一个默认的@At diff --git a/doc/manual/mvc/session.man b/doc/manual/mvc/session.man index ec42f15283..55f71b86da 100644 --- a/doc/manual/mvc/session.man +++ b/doc/manual/mvc/session.man @@ -60,6 +60,6 @@ Session机制是什么? -------------------------------------------------------------------------------------------- 哪里有现成的实现? - * [nutzmore项目 https://github.com/nutzam/nutzmore/tree/master/nutz-integration-shiro] + * [https://github.com/nutzam/nutzmore/tree/master/nutz-integration-shiro nutzmore项目 ] * [https://git.oschina.net/nutz/nutzmore/tree/master/nutz-integration-shiro Git@OSC镜像] * 有ShiroSessionProvider,使用Shiro的Session代替原生session \ No newline at end of file diff --git a/doc/manual/mvc/setupby.man b/doc/manual/mvc/setupby.man index 68275cbbe5..887a238cb0 100644 --- a/doc/manual/mvc/setupby.man +++ b/doc/manual/mvc/setupby.man @@ -10,7 +10,7 @@ ---------------------------------------------------------- 如何使用@SetupBy - 首先, 实现Setup接口.这个接口就2个方法,分别对应启动(init)和关闭(depose) + 首先, 实现Setup接口.这个接口就2个方法,分别对应启动(init)和关闭(destroy) {{{ public void init(NutConfig nc) { @@ -31,7 +31,7 @@ 那关闭的时候呢? {{{ - public void depose(NutConfig nc) { + public void destroy(NutConfig nc) { Ioc ioc = nc.getIoc(); // 可以拿到Ioc容器 Dao dao = ioc.get(Dao.class); diff --git a/doc/manual/mvc/view.man b/doc/manual/mvc/view.man index 5ae827855b..572ddbc088 100644 --- a/doc/manual/mvc/view.man +++ b/doc/manual/mvc/view.man @@ -418,7 +418,7 @@ Raw视图 || @Ok("raw:html") || 等价于@Ok("raw:text/html"); || || @Ok("raw:htm") || 等价于@Ok("raw:text/html"); || || @Ok("raw:stream") || 等价于@Ok("raw:application/octet-stream"); || - || @Ok("raw:json") || 等价于@Ok("raw:application/x-javascript"); || + || @Ok("raw:json") || 等价于@Ok("raw:application/json"); || || @Ok("raw:js") || 等价于@Ok("raw:application/x-javascript"); || || @Ok("raw:jpg") || 等价于@Ok("raw:image/jpeg"); || || @Ok("raw:png") || 等价于@Ok("raw:image/png"); || @@ -575,3 +575,18 @@ RawView2 [https://github.com/nutzam/nutzmore/tree/master/nutz-plugins-views 官方扩展集] [https://ibeetl.com ibeetl模板自带BeetlViewMaker] [http://nutzbook.wendal.net/templates/templates.html nutzbook中集成第三方模板引擎的描述] + +------------------------------------------------------------------------------------------ +扩展视图上下文默认变量 + 实现ViewContextCollector接口并将对象注入到ioc中,在渲染视图时会将定义的变量应用到模块上下文中。 + {{{ + @IocBean + public class DefaultViewContext implements ViewContextCollector { + @Override + public Context collect(HttpServletRequest req, Object obj) { + Context ctx = Lang.context(); + ctx.set("codeUtil", new CodeUtil()); + return ctx; + } + } + }}} \ No newline at end of file diff --git a/doc/manual/nutz_release_notes.man b/doc/manual/nutz_release_notes.man index f459a245f8..1174aa7e84 100644 --- a/doc/manual/nutz_release_notes.man +++ b/doc/manual/nutz_release_notes.man @@ -1,92 +1,69 @@ -#title: 1.r.62 发行注记 -#index:0,1 -#author:wendal(wendal1985@gmail.com) +#title: 1.r.66 发行注记 +#index: 0,1 +#author: 胖五(pangwu86@gmail.com) + -------------------------------------------------------------------------------------------------------- -1.r.62 {*黄皮} 发行注记(20170718) +1.r.66 {*风花雪月} 发行注记(20180615) - 黄皮,有点酸有点甜,但不是初恋的味道, 俺这种广州土著就很喜欢吃.去年7月北京之行, - 给同事带去的一箱黄皮和荔枝,结果,荔枝一边倒的好评, 黄皮是毁誉参半. - - 新功能新特性也是这样,有人更喜欢它的甜,也有人更在意它的酸. - - 这个版本, 新特性主要是"@IocBean/@Inject的扩展", 不到100行的代码, 对ioc的扩展性的提升蛮大的. - - 兼容性方面, 应该100%兼容1.r.61.r2, 一如既往的放心升级吧,现在还新增了"版本升级"的文档,收集升级中可能遇到的问题. - - 我们迎来了新的提交者[https://github.com/qinerg qinerg],他给nutz提交了几个修改,并在nutzmore添加了event和undertow等插件 - - 感谢elkan1788,hzl7652,tanqimin,l4dfire,qinerg,SkyMonkya,flakycov,jiyuefeng在github上提交的issue/pr, - 及论坛上新增的几百个帖子的作者,还有QQ群里的基友们, 各位都在以各种的方式推动着nutz的前进. ---------------------------------------------------------------------------------------------------------- -主要变化: - - * add: [https://github.com/nutzam/nutz/issues/1280 支持把@IocBean标注在方法上,作为工厂方法] by wendal - * add: [https://github.com/nutzam/nutz/issues/1267 MVC的Param中增加对自定义时间格式的解析] by [https://github.com/elkan1788 elkan1788] - * add: [https://github.com/nutzam/nutz/issues/1149 Daos.migration要支持Oracle和SqlServer的索引新增 ] by [https://github.com/hzl7652 hzl7652] , [https://github.com/tanqimin tanqimin],wendal - * add: [https://github.com/nutzam/nutz/pull/1256 dao支持nst] by [https://github.com/l4dfire l4dfire] - * add: [https://github.com/nutzam/nutz/issues/1268 Images支持图片水平、垂直翻转] by pangwu86 - * add: [https://github.com/nutzam/nutz/issues/1262 Images工具类加上水印] by pangwu86 - * fix: [https://github.com/nutzam/nutz/issues/1250 PropertiesProxy.print方法不work] by wendal - * fix: [https://github.com/nutzam/nutz/issues/1261 json转换08会报错] by [https://github.com/qinerg qinerg] - * fix: [https://github.com/nutzam/nutz/issues/1261 ChainPasing解析] by [https://github.com/qinerg qinerg] - * fix: [https://github.com/nutzam/nutz/issues/1271 使用Http工具类获取网页时出错] by [https://github.com/qinerg qinerg] - * fix: [https://github.com/nutzam/nutz/issues/1276 通过map进行插入操作时,设置主键和自增应该顺序无关] by wendal - * fix: [https://github.com/nutzam/nutz/issues/1277 MVC @Param 对象属性默认值df无效] by [https://github.com/SkyMonkya SkyMonkya] - * fix: [https://github.com/nutzam/nutz/issues/1283 HttpTest.test_weibo_post is flaky] by [https://github.com/flakycov flakycov] - * change: [https://github.com/nutzam/nutz/pull/1249 重写H2下获得表索引方法] by [https://github.com/jiyuefeng jiyuefeng] - * change: [https://github.com/nutzam/nutz/issues/1282 Dao初始化时引用的FilePool改为懒汉式] by [https://github.com/qinerg qinerg] + + 相隔了快5个月后,我们又回来了。 + + 首先吐槽下上次发布版本后定的一堆目标,到目前为止一个都没实现... + + 定好了4月来点动作,之后很自然的推迟到5月,又从5月很自然推迟到6月,然后6月已经过半了,还没影呢... + + + + 不过好在NutzBoot一直在wendal的带领下有条不紊的发着版本,非常牛逼非常给力!据说某平台的star数也已经超过了nutz... -------------------------------------------------------------------------------------------------------- -部分新功能介绍 - - 新增@IocBean实例工厂方法, 可以代替ioc js, 集成第三方类的时候更方便了, 详情查阅文档 [ioc/ioc_factory.man 工厂方法] - - {{{ -@IocBean // 首先,它自己必须加@IocBean, 可以使用@IocBean/@Inject的全部功能. -public class MyBeanFactory { - - @IocBean - public PropertiesProxy getConf() { - if ("product".equals(System.getProperty("nutz.runmode"))) { - return new PropertiesProxy("/etc/nutz/custom"); - } else { - return new PropertiesProxy("custom/"); - } - } - - // 生成一个名为dataSource的bean. 命名规则是: IocBean(name=XXX) > 方法名去掉get/build后首字母小写. - @IocBean - public SimpleDataSource getDataSource(@Inject("refer:conf")PropertiesProxy conf) { - SimpleDataSource ds = new SimpleDataSource(); - ds.setJdbcUrl(conf.get("db.url", "jdbc:h2:mem:nutztest")); - return ds; - } - - @IocBean - public Dao buildDao(DataSource dataSource) { // 带参数, 默认是按类型注入 - return new NutDao(dataSource); - } - }}} - --------------------------------------------------------------------------------------------------------- -文档更新 +关于接下来会做什么 + + 实际上我们准备的第一个动作是开个定期的直播活动,也偷偷的试播了2次 - 基于Wikipedia的要求, nutz的文档已全部应用 [http://zh.wikipedia.org/wiki/Wikipedia:CC-BY-SA-3.0协议文本 知识共享 署名-相同方式共享 3.0协议] 及 - [http://zh.wikipedia.org/wiki/Wikipedia:GNU自由文档许可证文本 GNU自由文档许可证], 上述协议并不影响大家的使用,请放心. + 准备了背景板也确定好了话题,然后就没有了下文,月播的计划也沦为季播甚至可能变成年播/(ㄒoㄒ)/~~ + + + + 总之我们痛定思痛,认为这个事情还是要提上日程,希望能在下个月(或下下个月)的某一天与你们见面吧 + + 当然还有官网与论坛的改版,已经在做了。只是目前做的东西还是属于内部开发阶段,不能公开,具体的进度也等着直播的时候跟大家好好聊聊。 + + 希望我们渡过这段非常时期后能恢复到日常较缓慢的节奏中,留出更多的精力去做些有趣的事情。 - * add: [http://nutzam.com/core/img/overview.html Images图片水平/垂直翻转/添加水印]的文档 by pangwu86 - * add: [http://nutzam.com/core/basic/version_upgrade.html 版本升级]的章节 - * add: [http://nutzam.com/core/ioc/ioc_factory.html Ioc工厂方法]独立为一个章节,并添加@IocBean实例工厂方法的说明 - * add: [http://nutzam.com/core/weixin/weixin_api.html 微信API] - * add: [http://nutzam.com/core/weixin/weixin_login.html 微信登录] -------------------------------------------------------------------------------------------------------- -详细列表: - - * [https://github.com/nutzam/nutz/issues?q=is%3Aissue+is%3Aclosed+milestone%3A1.r.62 issue@github] +题图注释 + + 老任的E3上发表了《火焰纹章》的新作,日版副标题是”风花雪夜“ 这起的可是非常的中国呀,要知道美版标题可是“Three Houses”(三间屋).... -欢迎访问[https://nutzam.com 官网] 及 [https://nutz.cn Nutz社区],以获取更多信息 + 总之这个好听的名字一下子打动了me,就顺利的成了这次的版本代号。 -[https://nutz.cn Nutz社区]已经累计了5000多帖子, 20000+条回复,平均回复时间少于10分钟哦,白天基本上秒回! +--------------------------------------------------------------------------------------------------------- +主要变化 + + 内容主要是小Feature和Bug修改,请放心升级 + * add: 缓存正则表达式 + * add: 统一考虑一下JDK8的LocalDate相关的支持 + * fix: NutDao.fastInsert字段解析不全 + * fix: dao.count()无法解析自定义sql拼接后的SimpleCriteria + * fix: Json.toJson 方法传入 Timestamp 类型时输出精度不正确的问题 + * fix: UploadAdaptor,服务每次启动总是会生成一个临时文件 + * fix: postgresql 获取 blob得到null + * fix: Mirror getAnnotation NPE + * fix: Mirror#findMethod 在抽象类中静态方法查找报错 + * fix: NutzDao insertOrUpdate(t) @Id的字段 id为包装器类型报错 + * fix: org.nutz.lang.Stopwatch 秒表意见 + * fix: DaoUpTest注释写错了吧 + * fix: 存在不同ioc不同classloader 相同 bean name 被错误缓存的bug + * fix: Nutz json 支持 final 字段 + * fix: Json: 支持JDK8的LocalDate和LocalDateTime + +-------------------------------------------------------------------------------------------------------- +详细列表 + + * [https://github.com/nutzam/nutz/issues?q=is%3Aissue+is%3Aclosed+milestone%3A1.r.66 Issue@github] + * 欢迎访问[https://nutzam.com 官网] 及 [https://nutz.cn Nutz社区],以获取更多信息 + * [https://nutz.cn Nutz社区]已经累计了6000多帖子, 30000+条回复,平均回复时间少于10分钟哦,白天基本上秒回! diff --git a/doc/manual/testing/junit_testing.man b/doc/manual/testing/junit_testing.man index 6ba0caca41..d138fb8362 100644 --- a/doc/manual/testing/junit_testing.man +++ b/doc/manual/testing/junit_testing.man @@ -20,12 +20,6 @@ 1.r.60 test - - junit - junit - 4.12 - test - org.mockito diff --git a/doc/manual/weixin/helloworld.man b/doc/manual/weixin/helloworld.man index 8b6c921522..bed3fe705f 100644 --- a/doc/manual/weixin/helloworld.man +++ b/doc/manual/weixin/helloworld.man @@ -30,7 +30,7 @@ Nutz 为 微信准备了什么? -------------------------------------------------------------------------------------------------------- 然后,你需要一个外网地址 - 也行你登录过nutzcn或者听说过,或者不知道它是什么鬼东西,没关系, 你现在要访问它 + 也许你登录过nutzcn或者听说过,或者不知道它是什么鬼东西,没关系, 你现在要访问它 地址: [https://nutz.cn/yvr/list NutzCN社区] 请使用github登录, 若没有github账号或者翻墙困难,可以使用QQ或微信登录. diff --git "a/doc/psd/01\346\255\243\347\211\207.png" "b/doc/psd/01\346\255\243\347\211\207.png" new file mode 100644 index 0000000000..9a4bea468b Binary files /dev/null and "b/doc/psd/01\346\255\243\347\211\207.png" differ diff --git "a/doc/psd/02\351\231\204\347\211\207.png" "b/doc/psd/02\351\231\204\347\211\207.png" new file mode 100644 index 0000000000..c336a540ef Binary files /dev/null and "b/doc/psd/02\351\231\204\347\211\207.png" differ diff --git "a/doc/psd/03\345\222\214\346\210\220\347\211\207.png" "b/doc/psd/03\345\222\214\346\210\220\347\211\207.png" new file mode 100644 index 0000000000..253ebffb8d Binary files /dev/null and "b/doc/psd/03\345\222\214\346\210\220\347\211\207.png" differ diff --git a/doc/psd/nutz-logo-w-word.psd b/doc/psd/nutz-logo-w-word.psd index f39ca7e3cd..5e41cfe47d 100644 Binary files a/doc/psd/nutz-logo-w-word.psd and b/doc/psd/nutz-logo-w-word.psd differ diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index deedc7fa5e..0000000000 Binary files a/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 390e87ec5c..0000000000 --- a/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Sun Sep 11 15:47:27 CST 2016 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=http\://cdn.nutz.cn/gradle-3.2-bin.zip diff --git a/gradlew.bat b/gradlew.bat deleted file mode 100644 index f9553162f1..0000000000 --- a/gradlew.bat +++ /dev/null @@ -1,84 +0,0 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/local.properties b/local.properties deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/pom.xml b/pom.xml index a49e06afef..4b458bd78e 100644 --- a/pom.xml +++ b/pom.xml @@ -1,259 +1,279 @@ - 4.0.0 - org.nutz - nutz - jar - Nutz - 1.r.63-SNAPSHOT - - UTF-8 - - Nutz, which is a collections of lightweight frameworks, each of them can be used independently + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + 4.0.0 + org.nutz + nutz + jar + Nutz + 1.r.74-SNAPSHOT + + UTF-8 + 9.4.34.v20201102 + + Nutz, which is a collections of lightweight frameworks, each of them can be used independently + - http://nutzam.com - - Github Issue - http://github.com/nutzam/nutz/issues - - - - The Apache Software License, Version 2.0 - http://apache.org/licenses/LICENSE-2.0.txt - - - - - zozoh - zozoh - zozohtnt@gmail.com - http://weibo.com/zozoh - - - wendal - Wendal Chen - wendal1985@gmail.com - http://wendal.net/ - - - juqkai - Juqkai - Bird.Wyatt@gmail.com - https://github.com/juqkai - - - - scm:git:git://github.com/nutzam/nutz.git - scm:git:git://github.com/nutzam/nutz.git - git://github.com/nutzam/nutz.git - + http://nutzam.com + + Github Issue + http://github.com/nutzam/nutz/issues + + + + The Apache Software License, Version 2.0 + http://apache.org/licenses/LICENSE-2.0.txt + + + + + zozoh + zozoh + zozohtnt@gmail.com + http://weibo.com/zozoh + + + wendal + Wendal Chen + wendal1985@gmail.com + http://wendal.net/ + + + juqkai + Juqkai + Bird.Wyatt@gmail.com + https://github.com/juqkai + + + + scm:git:git://github.com/nutzam/nutz.git + scm:git:git://github.com/nutzam/nutz.git + git://github.com/nutzam/nutz.git + - - - - - junit - junit - 4.8.2 - test - - - log4j - log4j - 1.2.17 - true - provided - - - com.h2database - h2 - 1.4.196 - test - - - javax.servlet - javax.servlet-api - 3.1.0 - provided - - - org.postgresql - postgresql - 9.4-1206-jdbc41 - test - - - mysql - mysql-connector-java - 5.1.40 - test - - - com.alibaba - druid - 1.0.27 - test - - - jconsole - com.alibaba - - - tools - com.alibaba - - - - - org.xerial - sqlite-jdbc - 3.8.11.2 - test - - - org.slf4j - slf4j-log4j12 - 1.7.24 - test - - - org.eclipse.jetty - jetty-servlets - 9.4.7.v20170914 - test - - - org.eclipse.jetty - jetty-webapp - 9.4.7.v20170914 - test - - - org.eclipse.jetty.websocket - websocket-server - 9.4.7.v20170914 - test - - - org.eclipse.jetty.websocket - javax-websocket-server-impl - 9.4.7.v20170914 - test - - - org.eclipse.jetty - jetty-jndi - - - - - org.eclipse.jetty - apache-jsp - 9.4.7.v20170914 - test - - - org.eclipse.jetty - apache-jstl - 9.4.7.v20170914 - test - - + + + + + junit + junit + 4.13.1 + test + + + log4j + log4j + 1.2.17 + true + provided + + + org.slf4j + slf4j-api + 1.7.25 + true + provided + + + com.h2database + h2 + 1.4.196 + test + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + org.postgresql + postgresql + 42.7.4 + test + + + com.mysql + mysql-connector-j + 8.2.0 + test + + + org.mariadb.jdbc + mariadb-java-client + 3.4.1 + test + + + com.yashandb + yashandb-jdbc + 1.7.10 + test + + + com.alibaba + druid + 1.2.25 + test + + + jconsole + com.alibaba + + + tools + com.alibaba + + + + + org.xerial + sqlite-jdbc + 3.8.11.2 + test + + + org.slf4j + slf4j-log4j12 + 1.7.25 + test + + + org.eclipse.jetty + jetty-servlets + ${jetty.version} + test + + + org.eclipse.jetty + jetty-webapp + ${jetty.version} + test + + + org.eclipse.jetty.websocket + websocket-server + ${jetty.version} + test + + + org.eclipse.jetty.websocket + javax-websocket-server-impl + ${jetty.version} + test + + + org.eclipse.jetty + jetty-jndi + + + + + org.eclipse.jetty + apache-jsp + ${jetty.version} + test + + + org.eclipse.jetty + apache-jstl + ${jetty.version} + test + + + com.microsoft.sqlserver + mssql-jdbc + 7.0.0.jre10 + test + + + org.skyscreamer + jsonassert + 1.5.0 + test + + - - ${project.basedir}/src - ${project.basedir}/test + + ${project.basedir}/src + ${project.basedir}/test - - - ${project.basedir}/src - false - - **/*.java - - - - - - ${project.basedir}/test - false - - **/*.java - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.7.0 - - 1.6 - 1.6 - - - - org.mortbay.jetty - jetty-maven-plugin - 7.6.16.v20140903 - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.10.4 - - -Xdoclint:none - - - - org.apache.maven.plugins - maven-jar-plugin - 3.0.2 - - - **/package-info.class - - - true - - true - - - - - - org.codehaus.mojo - cobertura-maven-plugin - 2.7 - - - html - xml - - - - - - org.apache.maven.plugins - maven-surefire-plugin - 2.20.1 - - false - - - - - - - nutzcn-snapshots - NutzCN snapshot repository - https://jfrog.nutz.cn/artifactory/snapshots - + + + ${project.basedir}/src + false + + **/*.java + + + + + + ${project.basedir}/test + false + + **/*.java + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.7.0 + + 8 + 8 + true + true - - sonatype-release-staging - Sonatype Nexus release repository - https://oss.sonatype.org/service/local/staging/deploy/maven2 - - + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.4 + + -Xdoclint:none + + + + org.apache.maven.plugins + maven-jar-plugin + 3.1.1 + + + **/package-info.class + + + true + + true + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0-M5 + + false + + + + + + + nutzcn-snapshots + NutzCN snapshot repository + https://jfrog.nutz.cn/artifactory/snapshots + + + + sonatype-release-staging + Sonatype Nexus release repository + https://oss.sonatype.org/service/local/staging/deploy/maven2 + + diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index 3e0012069b..0000000000 --- a/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = 'nutz' diff --git a/sonar.properties b/sonar.properties deleted file mode 100644 index 6b9c5f7171..0000000000 --- a/sonar.properties +++ /dev/null @@ -1,4 +0,0 @@ -sonar.projectKey=nutzam:nutz -sonar.projectName=Nutz Core -sonar.projectVersion=1.r.62 -sonar.language=java \ No newline at end of file diff --git a/src/org/nutz/Nutz.java b/src/org/nutz/Nutz.java index 3d58ca1018..afae16031e 100644 --- a/src/org/nutz/Nutz.java +++ b/src/org/nutz/Nutz.java @@ -33,10 +33,7 @@ public final class Nutz { * @return nutz 项目的版本号 */ public static String version() { - return String.format("%d.%s.%d-SNAPSHOT", - majorVersion(), - releaseLevel(), - minorVersion()); + return "1.r.73-SNAPSHOT"; } /** @@ -50,7 +47,7 @@ public static int majorVersion() { * 发布流水 */ public static int minorVersion() { - return 63; + return 69; } /** diff --git a/src/org/nutz/aop/AbstractClassAgent.java b/src/org/nutz/aop/AbstractClassAgent.java index a248799e34..825ab0d913 100644 --- a/src/org/nutz/aop/AbstractClassAgent.java +++ b/src/org/nutz/aop/AbstractClassAgent.java @@ -5,10 +5,10 @@ import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.atomic.AtomicLong; import org.nutz.lang.Lang; import org.nutz.lang.Mirror; +import org.nutz.lang.reflect.ReflectTool; /** * 提供ClassAgent的基础实现,拦截不可能插入Aop代码的Class @@ -25,12 +25,8 @@ public abstract class AbstractClassAgent implements ClassAgent { private ArrayList pairs = new ArrayList(); - - /** - * 这个属性仅限测试时重置类名用 - */ - @Deprecated - public static AtomicLong t; + + public String id; public ClassAgent addInterceptor(MethodMatcher matcher, MethodInterceptor listener) { if (null != listener) @@ -41,7 +37,9 @@ public ClassAgent addInterceptor(MethodMatcher matcher, MethodInterceptor listen public Class define(ClassDefiner cd, Class klass) { if (klass.getName().endsWith(CLASSNAME_SUFFIX)) return klass; - String newName = klass.getName() + (t == null ? "" : "$" + t.get()) + CLASSNAME_SUFFIX; + String newName = "org.nutz.lang.reflect"; + newName += "." + Lang.md5(klass.getName()); + newName += (id == null ? "" : "$" + id) + CLASSNAME_SUFFIX; return define(cd, klass, newName); } diff --git a/src/org/nutz/aop/asm/AopInvokeAdpter.java b/src/org/nutz/aop/asm/AopInvokeAdpter.java index 401298f311..a8cd4f1696 100644 --- a/src/org/nutz/aop/asm/AopInvokeAdpter.java +++ b/src/org/nutz/aop/asm/AopInvokeAdpter.java @@ -31,11 +31,11 @@ void visitCode() { mv.visitCode(); for (int i = 0; i < methodArray.length; i++) { - // start of fuck linenumber + // start of fuck line number Label tmp = new Label(); mv.visitLabel(tmp); mv.visitLineNumber(i+1, tmp); - // end of Linenumber + // end of line number Method method = methodArray[i]; mv.visitVarInsn(ILOAD, 1); visitX(i); diff --git a/src/org/nutz/aop/asm/AopMethodAdapter.java b/src/org/nutz/aop/asm/AopMethodAdapter.java index df7ba2c8b7..35571061ad 100644 --- a/src/org/nutz/aop/asm/AopMethodAdapter.java +++ b/src/org/nutz/aop/asm/AopMethodAdapter.java @@ -42,7 +42,7 @@ class AopMethodAdapter extends NormalMethodAdapter implements Opcodes { void enhandMethod_Void() { mv.visitCode(); - // start of fuck linenumber + // start of fuck line number Label tmp = new Label(); mv.visitLabel(tmp); mv.visitLineNumber(1, tmp); diff --git a/src/org/nutz/aop/asm/AsmClassAgent.java b/src/org/nutz/aop/asm/AsmClassAgent.java index 83d82675d5..07b28a5b73 100644 --- a/src/org/nutz/aop/asm/AsmClassAgent.java +++ b/src/org/nutz/aop/asm/AsmClassAgent.java @@ -6,10 +6,7 @@ import org.nutz.aop.AbstractClassAgent; import org.nutz.aop.ClassDefiner; import org.nutz.aop.MethodInterceptor; -import org.nutz.lang.Lang; import org.nutz.lang.Mirror; -import org.nutz.log.Logs; -import org.nutz.repo.org.objectweb.asm.Opcodes; /** * @@ -17,18 +14,10 @@ * */ public class AsmClassAgent extends AbstractClassAgent { - - static int CLASS_LEVEL = Opcodes.V1_5; static final String MethodArray_FieldName = "_$$Nut_methodArray"; static final String MethodInterceptorList_FieldName = "_$$Nut_methodInterceptorList"; - static { - if (Lang.isJDK6()) - CLASS_LEVEL = Opcodes.V1_6; - Logs.get().debugf("AsmClassAgent will define class in Version %s",CLASS_LEVEL); - } - @SuppressWarnings("unchecked") protected Class generate(ClassDefiner cd, Pair2[] pair2s, diff --git a/src/org/nutz/aop/asm/ChangeToChildConstructorMethodAdapter.java b/src/org/nutz/aop/asm/ChangeToChildConstructorMethodAdapter.java index 9f3723678a..4b6a865576 100644 --- a/src/org/nutz/aop/asm/ChangeToChildConstructorMethodAdapter.java +++ b/src/org/nutz/aop/asm/ChangeToChildConstructorMethodAdapter.java @@ -24,11 +24,11 @@ class ChangeToChildConstructorMethodAdapter extends NormalMethodAdapter { void visitCode() { mv.visitCode(); - // start of fuck linenumber + // start of fuck line number Label tmp = new Label(); mv.visitLabel(tmp); mv.visitLineNumber(1, tmp); - // end of Linenumber + // end of line number mv.visitVarInsn(ALOAD, 0); loadArgs(); mv.visitMethodInsn(INVOKESPECIAL, superClassName, "", desc, false); diff --git a/src/org/nutz/aop/asm/ClassY.java b/src/org/nutz/aop/asm/ClassY.java index d0ae0c7a2b..1529eecd3c 100644 --- a/src/org/nutz/aop/asm/ClassY.java +++ b/src/org/nutz/aop/asm/ClassY.java @@ -5,6 +5,7 @@ import java.lang.reflect.Modifier; import org.nutz.aop.AopCallback; +import org.nutz.conf.NutConf; import org.nutz.repo.org.objectweb.asm.ClassWriter; import org.nutz.repo.org.objectweb.asm.MethodVisitor; import org.nutz.repo.org.objectweb.asm.Opcodes; @@ -33,7 +34,7 @@ class ClassY implements Opcodes { this.myName = myName.replace('.', '/'); this.enhancedSuperName = klass.getName().replace('.', '/'); this.cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); - cw.visit( AsmClassAgent.CLASS_LEVEL, + cw.visit( NutConf.AOP_CLASS_LEVEL, ACC_PUBLIC, this.myName, null, diff --git a/src/org/nutz/aop/interceptor/ioc/TransIocLoader.java b/src/org/nutz/aop/interceptor/ioc/TransIocLoader.java index b0f4c03bd7..47b03a1ed7 100644 --- a/src/org/nutz/aop/interceptor/ioc/TransIocLoader.java +++ b/src/org/nutz/aop/interceptor/ioc/TransIocLoader.java @@ -2,15 +2,9 @@ import java.io.StringReader; -import org.nutz.ioc.IocLoader; -import org.nutz.ioc.IocLoading; -import org.nutz.ioc.ObjectLoadException; import org.nutz.ioc.loader.json.JsonLoader; -import org.nutz.ioc.meta.IocObject; -public class TransIocLoader implements IocLoader { - - protected JsonLoader proxy; +public class TransIocLoader extends JsonLoader { public TransIocLoader() { StringBuilder sb = new StringBuilder("{"); @@ -20,18 +14,6 @@ public TransIocLoader() { sb.append("txREPEATABLE_READ: {type : 'org.nutz.aop.interceptor.TransactionInterceptor',args : [4]},\n"); sb.append("txSERIALIZABLE: {type : 'org.nutz.aop.interceptor.TransactionInterceptor',args : [8]},"); sb.setCharAt(sb.length() - 1, '}'); - proxy = new JsonLoader(new StringReader(sb.toString())); - } - - public String[] getName() { - return proxy.getName(); - } - - public IocObject load(IocLoading loading, String name) throws ObjectLoadException { - return proxy.load(loading, name); - } - - public boolean has(String name) { - return proxy.has(name); + loadFromReader(new StringReader(sb.toString())); } } diff --git a/src/org/nutz/aop/package-info.java b/src/org/nutz/aop/package-info.java index a9f0eaac83..23ac5f01fc 100644 --- a/src/org/nutz/aop/package-info.java +++ b/src/org/nutz/aop/package-info.java @@ -1,7 +1,7 @@ /** * 提供对 Java 类的拦截能力 *

- * 通过 MeothodInterceptor 接口,对于 Java 类 public | protected 函数的提供了拦截能力。 + * 通过 MethodInterceptor 接口,对于 Java 类 public | protected 函数的提供了拦截能力。 * 具体的做法是为被拦截类生成子类,并通过 ASM 生成字节码 */ package org.nutz.aop; \ No newline at end of file diff --git a/src/org/nutz/castor/Castors.java b/src/org/nutz/castor/Castors.java index 8e4f2acd60..59e3dafd1c 100644 --- a/src/org/nutz/castor/Castors.java +++ b/src/org/nutz/castor/Castors.java @@ -12,6 +12,7 @@ import java.util.concurrent.ConcurrentHashMap; import org.nutz.castor.castor.Object2Object; +import org.nutz.conf.NutConf; import org.nutz.lang.Lang; import org.nutz.lang.Mirror; import org.nutz.lang.TypeExtractor; @@ -455,6 +456,22 @@ public String castToString(Object src) { defaultCastorList.add(org.nutz.castor.castor.Timestamp2SqlTime.class); defaultCastorList.add(org.nutz.castor.castor.Timestamp2String.class); defaultCastorList.add(org.nutz.castor.castor.String2DateFormat.class); + + defaultCastorList.add(org.nutz.castor.castor.String2TmInfo.class); + defaultCastorList.add(org.nutz.castor.castor.Integer2TmInfo.class); + defaultCastorList.add(org.nutz.castor.castor.Long2TmInfo.class); + defaultCastorList.add(org.nutz.castor.castor.TmInfo2String.class); + defaultCastorList.add(org.nutz.castor.castor.TmInfo2Long.class); + defaultCastorList.add(org.nutz.castor.castor.TmInfo2Integer.class); + + if (NutConf.HAS_LOCAL_DATE_TIME) { + defaultCastorList.add(org.nutz.castor.castor.String2LocalDateTime.class); + defaultCastorList.add(org.nutz.castor.castor.String2LocalTime.class); + defaultCastorList.add(org.nutz.castor.castor.String2LocalDate.class); + defaultCastorList.add(org.nutz.castor.castor.LocalDate2String.class); + defaultCastorList.add(org.nutz.castor.castor.LocalTime2String.class); + defaultCastorList.add(org.nutz.castor.castor.LocalDateTime2String.class); + } } private static Castors one = new Castors(); diff --git a/src/org/nutz/castor/castor/Enum2Number.java b/src/org/nutz/castor/castor/Enum2Number.java index 5482878c66..c119068b7f 100644 --- a/src/org/nutz/castor/castor/Enum2Number.java +++ b/src/org/nutz/castor/castor/Enum2Number.java @@ -19,6 +19,9 @@ public Number cast(Enum src, Class toType, String... args) // 如果失败,就用其顺序号 catch (Exception e) { Integer re = src.ordinal(); + if (toType.isPrimitive() || toType.equals(Integer.class) || toType.isAssignableFrom(Number.class)) { + return re; + } return (Number) Mirror.me(toType).born(re.toString()); } } diff --git a/src/org/nutz/castor/castor/Integer2TmInfo.java b/src/org/nutz/castor/castor/Integer2TmInfo.java new file mode 100644 index 0000000000..5a935ceecb --- /dev/null +++ b/src/org/nutz/castor/castor/Integer2TmInfo.java @@ -0,0 +1,19 @@ +package org.nutz.castor.castor; + +import org.nutz.castor.Castor; +import org.nutz.castor.FailToCastObjectException; +import org.nutz.lang.Times; +import org.nutz.lang.Times.TmInfo; + +public class Integer2TmInfo extends Castor { + + @Override + public TmInfo cast(Integer src, Class toType, String... args) + throws FailToCastObjectException { + if (null != src) { + return Times.Ti(src); + } + return null; + } + +} diff --git a/src/org/nutz/castor/castor/LocalDate2String.java b/src/org/nutz/castor/castor/LocalDate2String.java new file mode 100644 index 0000000000..8353eb2108 --- /dev/null +++ b/src/org/nutz/castor/castor/LocalDate2String.java @@ -0,0 +1,15 @@ +package org.nutz.castor.castor; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +import org.nutz.castor.Castor; + +public class LocalDate2String extends Castor { + + @Override + public String cast(LocalDate src, Class toType, String... args) { + return DateTimeFormatter.ofPattern("yyyy-MM-dd").format(src); + } + +} diff --git a/src/org/nutz/castor/castor/LocalDateTime2String.java b/src/org/nutz/castor/castor/LocalDateTime2String.java new file mode 100644 index 0000000000..59648a707b --- /dev/null +++ b/src/org/nutz/castor/castor/LocalDateTime2String.java @@ -0,0 +1,23 @@ +package org.nutz.castor.castor; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +public class LocalDateTime2String extends DateTimeCastor { + + private String format = "yyyy-MM-dd HH:mm:ss"; + + public String getFormat() { + return format; + } + + public void setFormat(String format) { + this.format = format; + } + + @Override + public String cast(LocalDateTime src, Class toType, String... args) { + return DateTimeFormatter.ofPattern(format).format(src); + } + +} diff --git a/src/org/nutz/castor/castor/LocalTime2String.java b/src/org/nutz/castor/castor/LocalTime2String.java new file mode 100644 index 0000000000..7ae706df69 --- /dev/null +++ b/src/org/nutz/castor/castor/LocalTime2String.java @@ -0,0 +1,15 @@ +package org.nutz.castor.castor; + +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; + +import org.nutz.castor.Castor; + +public class LocalTime2String extends Castor { + + @Override + public String cast(LocalTime src, Class toType, String... args) { + return DateTimeFormatter.ofPattern("HH:mm:ss.SSS").format(src); + } + +} diff --git a/src/org/nutz/castor/castor/Long2TmInfo.java b/src/org/nutz/castor/castor/Long2TmInfo.java new file mode 100644 index 0000000000..0d8aea0a8b --- /dev/null +++ b/src/org/nutz/castor/castor/Long2TmInfo.java @@ -0,0 +1,17 @@ +package org.nutz.castor.castor; + +import org.nutz.castor.Castor; +import org.nutz.lang.Times; +import org.nutz.lang.Times.TmInfo; + +public class Long2TmInfo extends Castor { + + @Override + public TmInfo cast(Long src, Class toType, String... args) { + if (null != src) { + return Times.Tims(src); + } + return null; + } + +} diff --git a/src/org/nutz/castor/castor/Number2Enum.java b/src/org/nutz/castor/castor/Number2Enum.java index 69eb4e8fa7..1a5839cb27 100644 --- a/src/org/nutz/castor/castor/Number2Enum.java +++ b/src/org/nutz/castor/castor/Number2Enum.java @@ -26,7 +26,17 @@ public Enum cast(Number src, Class toType, String... args) } } catch (Exception e) {} - + // 再试图用采用该类型的 from 的静态方法 + if (null == o) { + try { + Method m = toType.getMethod("from", int.class); + if (Modifier.isStatic(m.getModifiers()) + && toType.isAssignableFrom(m.getReturnType())) { + o = (Enum) m.invoke(null, v); + } + } catch (Exception e) { + } + } // 搞不定,则试图根据顺序号获取 if (null == o) try { diff --git a/src/org/nutz/castor/castor/Number2LocalDatetime.java b/src/org/nutz/castor/castor/Number2LocalDatetime.java new file mode 100644 index 0000000000..35ec8bc316 --- /dev/null +++ b/src/org/nutz/castor/castor/Number2LocalDatetime.java @@ -0,0 +1,24 @@ +package org.nutz.castor.castor; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.temporal.TemporalAccessor; +import java.util.Date; + +import org.nutz.castor.Castor; + +public class Number2LocalDatetime extends Castor { + + @Override + public TemporalAccessor cast(Number src, Class toType, String... args) { + Date date = new Date(src.longValue()); + LocalDateTime dt = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()); + if (toType == LocalDateTime.class) + return dt; + if (toType == LocalDate.class) + return dt.toLocalDate(); + return dt.toLocalTime(); + } + +} diff --git a/src/org/nutz/castor/castor/String2BigDecimal.java b/src/org/nutz/castor/castor/String2BigDecimal.java index e223326081..fb9f3b42d8 100644 --- a/src/org/nutz/castor/castor/String2BigDecimal.java +++ b/src/org/nutz/castor/castor/String2BigDecimal.java @@ -9,6 +9,16 @@ protected BigDecimal getPrimitiveDefaultValue() { return new BigDecimal(0); } + @Override + protected BigDecimal getFalseValue() { + return BigDecimal.ZERO; + } + + @Override + protected BigDecimal getTrueValue() { + return BigDecimal.ONE; + } + @Override protected BigDecimal valueOf(String str) { return new BigDecimal(str); diff --git a/src/org/nutz/castor/castor/String2Byte.java b/src/org/nutz/castor/castor/String2Byte.java index 39095a2549..e9d3162744 100644 --- a/src/org/nutz/castor/castor/String2Byte.java +++ b/src/org/nutz/castor/castor/String2Byte.java @@ -9,6 +9,16 @@ protected Byte getPrimitiveDefaultValue() { return (byte) 0; } + @Override + protected Byte getFalseValue() { + return 0; + } + + @Override + protected Byte getTrueValue() { + return 1; + } + @Override protected Byte valueOf(String str) { Nums.Radix ni = Nums.evalRadix(str); diff --git a/src/org/nutz/castor/castor/String2Double.java b/src/org/nutz/castor/castor/String2Double.java index 39d7c04bb2..8111441c2c 100644 --- a/src/org/nutz/castor/castor/String2Double.java +++ b/src/org/nutz/castor/castor/String2Double.java @@ -7,6 +7,16 @@ protected Double getPrimitiveDefaultValue() { return 0.0; } + @Override + protected Double getFalseValue() { + return (double) 0; + } + + @Override + protected Double getTrueValue() { + return (double) 1; + } + @Override protected Double valueOf(String str) { return Double.valueOf(str); diff --git a/src/org/nutz/castor/castor/String2Enum.java b/src/org/nutz/castor/castor/String2Enum.java index b02420eb6f..5c46352cbd 100644 --- a/src/org/nutz/castor/castor/String2Enum.java +++ b/src/org/nutz/castor/castor/String2Enum.java @@ -1,9 +1,14 @@ package org.nutz.castor.castor; import org.nutz.castor.Castor; +import org.nutz.castor.Castors; import org.nutz.castor.FailToCastObjectException; +import org.nutz.lang.Mirror; import org.nutz.lang.Strings; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + @SuppressWarnings({"rawtypes"}) public class String2Enum extends Castor { @@ -14,13 +19,18 @@ public Enum cast(String src, Class toType, String... args) throws FailToCastO return null; try { return Enum.valueOf((Class) toType, src); - } - catch (IllegalArgumentException e) { - for (Object c : toType.getEnumConstants()) { - if (c.toString().equals(src)) return (Enum) c; + } catch (IllegalArgumentException e) { + try { + Mirror me = Mirror.me(toType); + Field value = me.getField("value"); + Method from = toType.getMethod("from", value.getType()); + return (Enum) from.invoke(null, Castors.me().castTo(src, value.getType())); + } catch (Exception e1) { + for (Object c : toType.getEnumConstants()) { + if (c.toString().equals(src)) return (Enum) c; + } + throw e; } - - throw e; } } diff --git a/src/org/nutz/castor/castor/String2Float.java b/src/org/nutz/castor/castor/String2Float.java index 83074d4562..3fac731f63 100644 --- a/src/org/nutz/castor/castor/String2Float.java +++ b/src/org/nutz/castor/castor/String2Float.java @@ -7,6 +7,16 @@ protected Float getPrimitiveDefaultValue() { return 0.0f; } + @Override + protected Float getFalseValue() { + return (float) 0; + } + + @Override + protected Float getTrueValue() { + return (float)1; + } + @Override protected Float valueOf(String str) { return Float.valueOf(str); diff --git a/src/org/nutz/castor/castor/String2Integer.java b/src/org/nutz/castor/castor/String2Integer.java index d422962bff..be79140b83 100644 --- a/src/org/nutz/castor/castor/String2Integer.java +++ b/src/org/nutz/castor/castor/String2Integer.java @@ -9,10 +9,25 @@ protected Integer getPrimitiveDefaultValue() { return 0; } + @Override + protected Integer getFalseValue() { + return 0; + } + + @Override + protected Integer getTrueValue() { + return 1; + } + @Override protected Integer valueOf(String str) { Nums.Radix ni = Nums.evalRadix(str); - return Integer.valueOf(ni.val, ni.radix); + try { + return Integer.valueOf(ni.val, ni.radix); + } + catch (NumberFormatException e) { + return Double.valueOf(str).intValue(); + } } } diff --git a/src/org/nutz/castor/castor/String2LocalDate.java b/src/org/nutz/castor/castor/String2LocalDate.java new file mode 100644 index 0000000000..5d05154018 --- /dev/null +++ b/src/org/nutz/castor/castor/String2LocalDate.java @@ -0,0 +1,16 @@ +package org.nutz.castor.castor; + +import java.time.LocalDate; + +import org.nutz.lang.Strings; + +public class String2LocalDate extends DateTimeCastor { + + @Override + public LocalDate cast(String src, Class toType, String... args) { + // 处理空白 + if (Strings.isBlank(src)) + return null; + return LocalDate.parse(src); + } +} diff --git a/src/org/nutz/castor/castor/String2LocalDateTime.java b/src/org/nutz/castor/castor/String2LocalDateTime.java new file mode 100644 index 0000000000..29f46b1b85 --- /dev/null +++ b/src/org/nutz/castor/castor/String2LocalDateTime.java @@ -0,0 +1,17 @@ +package org.nutz.castor.castor; + +import java.time.LocalDateTime; +import java.time.ZoneId; + +import org.nutz.lang.Strings; + +public class String2LocalDateTime extends DateTimeCastor { + + @Override + public LocalDateTime cast(String src, Class toType, String... args) { + // 处理空白 + if (Strings.isBlank(src)) + return null; + return LocalDateTime.ofInstant(toDate(src).toInstant(), ZoneId.systemDefault()); + } +} diff --git a/src/org/nutz/castor/castor/String2LocalTime.java b/src/org/nutz/castor/castor/String2LocalTime.java new file mode 100644 index 0000000000..82e9740ab3 --- /dev/null +++ b/src/org/nutz/castor/castor/String2LocalTime.java @@ -0,0 +1,16 @@ +package org.nutz.castor.castor; + +import java.time.LocalTime; + +import org.nutz.lang.Strings; + +public class String2LocalTime extends DateTimeCastor { + + @Override + public LocalTime cast(String src, Class toType, String... args) { + // 处理空白 + if (Strings.isBlank(src)) + return null; + return LocalTime.parse(src); + } +} diff --git a/src/org/nutz/castor/castor/String2Long.java b/src/org/nutz/castor/castor/String2Long.java index e6ccbbc0f8..8c2522db57 100644 --- a/src/org/nutz/castor/castor/String2Long.java +++ b/src/org/nutz/castor/castor/String2Long.java @@ -9,10 +9,25 @@ protected Long getPrimitiveDefaultValue() { return 0L; } + @Override + protected Long getFalseValue() { + return 0L; + } + + @Override + protected Long getTrueValue() { + return 1L; + } + @Override protected Long valueOf(String str) { - Nums.Radix ni = Nums.evalRadix(str); - return Long.valueOf(ni.val, ni.radix); + try { + Nums.Radix ni = Nums.evalRadix(str); + return Long.valueOf(ni.val, ni.radix); + } + catch (NumberFormatException e) { + return Double.valueOf(str).longValue(); + } } } diff --git a/src/org/nutz/castor/castor/String2Number.java b/src/org/nutz/castor/castor/String2Number.java index 0e52bc67e7..fbee253508 100644 --- a/src/org/nutz/castor/castor/String2Number.java +++ b/src/org/nutz/castor/castor/String2Number.java @@ -23,6 +23,8 @@ protected boolean _isNull(String str) { } protected abstract T getPrimitiveDefaultValue(); + protected abstract T getFalseValue(); + protected abstract T getTrueValue(); protected abstract T valueOf(String str); @@ -35,6 +37,19 @@ public T cast(String src, Class toType, String... args) { && ("null".equals(src) || "NULL".equals(src) || "Null".equals(src))) { return null; } + + + if ("true".equalsIgnoreCase(src)) { + + return getTrueValue(); + } + + + if ("false".equalsIgnoreCase(src)) { + + return getFalseValue(); + } + try { return valueOf(src); } diff --git a/src/org/nutz/castor/castor/String2Short.java b/src/org/nutz/castor/castor/String2Short.java index e1254b13a0..8dd8b3a03b 100644 --- a/src/org/nutz/castor/castor/String2Short.java +++ b/src/org/nutz/castor/castor/String2Short.java @@ -9,6 +9,16 @@ protected Short getPrimitiveDefaultValue() { return 0; } + @Override + protected Short getFalseValue() { + return 0; + } + + @Override + protected Short getTrueValue() { + return 1; + } + @Override protected Short valueOf(String str) { Nums.Radix ni = Nums.evalRadix(str); diff --git a/src/org/nutz/castor/castor/String2TmInfo.java b/src/org/nutz/castor/castor/String2TmInfo.java new file mode 100644 index 0000000000..ae2be6d87b --- /dev/null +++ b/src/org/nutz/castor/castor/String2TmInfo.java @@ -0,0 +1,19 @@ +package org.nutz.castor.castor; + +import org.nutz.castor.Castor; +import org.nutz.castor.FailToCastObjectException; +import org.nutz.lang.Times; +import org.nutz.lang.Times.TmInfo; + +public class String2TmInfo extends Castor { + + @Override + public TmInfo cast(String src, Class toType, String... args) + throws FailToCastObjectException { + if(null!=src) { + return Times.Ti(src); + } + return null; + } + +} diff --git a/src/org/nutz/castor/castor/TmInfo2Integer.java b/src/org/nutz/castor/castor/TmInfo2Integer.java new file mode 100644 index 0000000000..0295ccbce8 --- /dev/null +++ b/src/org/nutz/castor/castor/TmInfo2Integer.java @@ -0,0 +1,16 @@ +package org.nutz.castor.castor; + +import org.nutz.castor.Castor; +import org.nutz.lang.Times.TmInfo; + +public class TmInfo2Integer extends Castor { + + @Override + public Integer cast(TmInfo src, Class toType, String... args) { + if (null != src) { + return src.value; + } + return null; + } + +} diff --git a/src/org/nutz/castor/castor/TmInfo2Long.java b/src/org/nutz/castor/castor/TmInfo2Long.java new file mode 100644 index 0000000000..a1be91dae0 --- /dev/null +++ b/src/org/nutz/castor/castor/TmInfo2Long.java @@ -0,0 +1,16 @@ +package org.nutz.castor.castor; + +import org.nutz.castor.Castor; +import org.nutz.castor.FailToCastObjectException; +import org.nutz.lang.Times.TmInfo; + +public class TmInfo2Long extends Castor { + + @Override + public Long cast(TmInfo src, Class toType, String... args) throws FailToCastObjectException { + if (null != src) + return (long) src.valueInMillisecond; + return null; + } + +} diff --git a/src/org/nutz/castor/castor/TmInfo2String.java b/src/org/nutz/castor/castor/TmInfo2String.java new file mode 100644 index 0000000000..188a14c90d --- /dev/null +++ b/src/org/nutz/castor/castor/TmInfo2String.java @@ -0,0 +1,27 @@ +package org.nutz.castor.castor; + +import org.nutz.castor.Castor; +import org.nutz.castor.FailToCastObjectException; +import org.nutz.lang.Times.TmInfo; + +public class TmInfo2String extends Castor { + + private String format = "HH:mm:ss"; + + public String getFormat() { + return format; + } + + public void setFormat(String format) { + this.format = format; + } + + @Override + public String cast(TmInfo src, Class toType, String... args) + throws FailToCastObjectException { + if (null != src) + return src.toString(format); + return null; + } + +} diff --git a/src/org/nutz/conf/NutConf.java b/src/org/nutz/conf/NutConf.java index c2646321aa..b941b33e9a 100644 --- a/src/org/nutz/conf/NutConf.java +++ b/src/org/nutz/conf/NutConf.java @@ -12,6 +12,7 @@ import org.nutz.log.Log; import org.nutz.log.Logs; import org.nutz.mapl.Mapl; +import org.nutz.repo.org.objectweb.asm.Opcodes; import org.nutz.resource.NutResource; import org.nutz.resource.Scans; @@ -47,8 +48,9 @@ public class NutConf { private static NutConf me() { if (null == conf) { synchronized (NutConf.class) { - if (null == conf) + if (null == conf) { conf = new NutConf(); + } } } return conf; @@ -79,7 +81,7 @@ private void loadResource(String... paths) { Map m = (Map) obj; map = (Map) Mapl.merge(map, m); for (Object key : m.keySet()) { - if (key.equals("include")) { + if ("include".equals(key)) { map.remove("include"); List include = (List) m.get("include"); loadResource(include.toArray(new String[include.size()])); @@ -88,8 +90,9 @@ private void loadResource(String... paths) { } } catch (Throwable e) { - if (log.isWarnEnabled()) + if (log.isWarnEnabled()) { log.warn("Fail to load config?! for " + nr.getName(), e); + } } } } @@ -133,8 +136,68 @@ public static void clear() { conf = null; } + /** + * 是否启用FastClass机制,会提高反射的性能,如果需要热部署,应关闭. 性能影响低于10% + */ public static boolean USE_FASTCLASS = !Lang.isAndroid && Lang.JdkTool.getMajorVersion() <= 8; + /** + * 是否缓存Mirror,配合FastClass机制使用,会提高反射的性能,如果需要热部署,应关闭. 性能影响低于10% + */ public static boolean USE_MIRROR_CACHE = true; + /** + * Map.map2object时的EL支持,很少会用到,所以默认关闭. 若启用, Json.fromJson会有30%左右的性能损失 + */ public static boolean USE_EL_IN_OBJECT_CONVERT = false; + /** + * 调试Scans类的开关.鉴于Scans已经非常靠谱,这个开关基本上没用处了 + */ public static boolean RESOURCE_SCAN_TRACE = false; + /** + * 是否允许非法的Json转义符,属于兼容性配置 + */ + public static boolean JSON_ALLOW_ILLEGAL_ESCAPE = true; + /** + * 若允许非法的Json转义符,是否把转义符附加进目标字符串 + */ + public static boolean JSON_APPEND_ILLEGAL_ESCAPE = false; + /** + * Aop类是否每个Ioc容器都唯一,设置这个开关是因为wendal还不确定会有什么影响,暂时关闭状态. + */ + public static boolean AOP_USE_CLASS_ID = false; + + public static int AOP_CLASS_LEVEL = Opcodes.V1_6; + + public static boolean HAS_LOCAL_DATE_TIME; + static { + try { + Class.forName("java.time.temporal.TemporalAccessor"); + HAS_LOCAL_DATE_TIME = true; + } + catch (Throwable e) { + } + } + + public static boolean AOP_ENABLED = !"false".equals(System.getProperty("nutz.aop.enable")); + + public static void set(String key, Object value) { + if (value == null) { + me().map.remove(key); + } else { + me().map.put(key, value); + } + } + + public static Object getOrDefault(String key, Object defaultValue) { + Object re = me().map.get(key); + if (re == null) { + return defaultValue; + } + return re; + } + + public static boolean SQLSERVER_USE_NVARCHAR = true; + + public static boolean DAO_USE_POJO_INTERCEPTOR = true; + + public static boolean MVC_ADD_ATTR_$REQUEST = false; } diff --git a/src/org/nutz/dao/Chain.java b/src/org/nutz/dao/Chain.java index 5159d38c4b..c65cc9d12b 100644 --- a/src/org/nutz/dao/Chain.java +++ b/src/org/nutz/dao/Chain.java @@ -1,7 +1,7 @@ package org.nutz.dao; +import java.io.Serializable; import java.lang.reflect.Field; -import java.util.LinkedHashMap; import java.util.Map; import org.nutz.dao.entity.Entity; @@ -12,6 +12,7 @@ import org.nutz.lang.Mirror; import org.nutz.lang.Strings; import org.nutz.lang.util.Callback2; +import org.nutz.lang.util.NutMap; /** * 名值链。 @@ -22,8 +23,10 @@ * @author Wendal(wendal1985@gmail.com) * @author lzxz1234 */ -public abstract class Chain { +public abstract class Chain implements Serializable { + private static final long serialVersionUID = 1L; + /** * 建立一条名值链开始的一环 * @@ -295,15 +298,15 @@ public static Chain makeSpecial(String name, Object value) { return chain; } - public static class DefaultChain extends Chain { - private Entry head; - private Entry current; - private Entry tail; + public static class DefaultChain extends Chain implements Serializable { + private static final long serialVersionUID = 1L; + private ChainEntry head; + private ChainEntry current; + private ChainEntry tail; private int size; public DefaultChain(String name, Object value) { - - this.head = new Entry(name, value); + this.head = new ChainEntry(name, value); this.current = head; this.tail = head; this.size = 1; @@ -327,7 +330,7 @@ public ValueAdaptor adaptor() { return current.adaptor; } public Chain add(String name, Object value) { - tail.next = new Entry(name, value); + tail.next = new ChainEntry(name, value); tail = tail.next; size ++; return this; @@ -355,7 +358,7 @@ public boolean special() { return current.special; } public boolean isSpecial() { - Entry entry = head; + ChainEntry entry = head; do { if(entry.special) { return true; @@ -364,17 +367,19 @@ public boolean isSpecial() { return false; } public Map toMap() { - Map map = new LinkedHashMap(); - Entry current = head; + NutMap map = new NutMap(); + ChainEntry current = head; while (current != null) { map.put(current.name, current.value); + if (current.adaptor != null) + map.put("."+current.name+".adaptor", current.adaptor); current = current.next; } return map; } public Chain updateBy(Entity entity) { if (null != entity) { - Entry current = head; + ChainEntry current = head; while (current != null) { MappingField ef = entity.getField(current.name); if (null != ef) { @@ -388,7 +393,7 @@ public Chain updateBy(Entity entity) { public T toObject(Class classOfT) { Mirror mirror = Mirror.me(classOfT); T re = mirror.born(); - Entry current = head; + ChainEntry current = head; while (current != null) { mirror.setValue(re, current.name, current.value); current = current.next; @@ -397,13 +402,14 @@ public T toObject(Class classOfT) { } } - public static class Entry { + public static class ChainEntry implements Serializable { + private static final long serialVersionUID = 1L; protected String name; - Object value; - ValueAdaptor adaptor; - boolean special; - Entry next; - public Entry(String name, Object value) { + protected Object value; + protected transient ValueAdaptor adaptor; + protected boolean special; + protected ChainEntry next; + public ChainEntry(String name, Object value) { this.name = name; this.value = value; } diff --git a/src/org/nutz/dao/Cnd.java b/src/org/nutz/dao/Cnd.java index 222ef2ab5c..b6917506b0 100644 --- a/src/org/nutz/dao/Cnd.java +++ b/src/org/nutz/dao/Cnd.java @@ -19,20 +19,23 @@ import org.nutz.dao.util.cri.SimpleCriteria; import org.nutz.dao.util.cri.SqlExpression; import org.nutz.dao.util.cri.SqlExpressionGroup; +import org.nutz.dao.util.lambda.LambdaQuery; +import org.nutz.dao.util.lambda.PFun; +import org.nutz.lang.Lang; import org.nutz.lang.Strings; import org.nutz.lang.segment.CharSegment; import org.nutz.lang.util.Callback2; /** * 是 Condition 的一个实现,这个类给你比较方便的方法来构建 Condition 接口的实例。 - * + * *

在 Dao 接口中使用


- * + * * 比如一个通常的查询: *

* List pets = dao.query(Pet.class, * Cnd.where("name","LIKE","B%").asc("name"), null); - * + * *

链式赋值示例


* Cnd.where("id", ">", 34).and("name","LIKE","T%").asc("name");
* 相当于
@@ -41,18 +44,24 @@ * Cnd.orderBy().desc("id");
* 相当于
* ORDER BY id DESC - * - *

带括号的条件语句 where (name="wendal" or age<18) and location != "地球"

+ * + *

+ * 带括号的条件语句 where (name="wendal" or age<18) and location != "地球" + *

* Cnd.where(Cnd.exps("name", "=", "wendal").or("age", "<", 18)).and("location", "!=", "地球") - * - *

静态条件,直接拼入sql,不做任何转义. Oracle的日期传Date对象,而非用to_date等数据库方法

+ * + *

+ * 静态条件,直接拼入sql,不做任何转义. Oracle的日期传Date对象,而非用to_date等数据库方法 + *

* Cnd.where(new Static("ct < to_date('2015-06-26')")).and(...........) *

- * - *

between用法

+ * + *

+ * between用法 + *

* Cnd.where("age", "between", new Object[]{19,29}).and(...........) *

- * + * *

你还需要知道的是:


*
    *
  • 你设置的字段名,是 java 的字段名 -- 如果 Entity 里有,那么会被转换成数据库字段名 @@ -60,9 +69,9 @@ *
  • 你的值,如果是字符串,或者其他类字符串对象(某种 CharSequence),那么在转换成 SQL 时,会正确被单引号包裹 *
  • 你的值如果是不可理解的自定义对象,会被转化成字符串处理 *
- * + * * @author zozoh(zozohtnt@gmail.com) - * + * @author 蛋蛋 [TopCoderMyDream@gmail.com] * @see org.nutz.dao.Condition */ public class Cnd implements OrderBy, Criteria, GroupBy { @@ -71,64 +80,118 @@ public class Cnd implements OrderBy, Criteria, GroupBy { /** * 用字符串和参数格式化出一个条件语句,注意,不会抹除特殊字符 - * @param format sql条件 - * @param args 参数 + * + * @param format + * sql条件 + * @param args + * 参数 * @return 条件对象 */ public static Condition format(String format, Object... args) { - return Strings.isBlank(format) ? null : new SimpleCondition(format, - args); + return Strings.isBlank(format) ? null + : new SimpleCondition(format, + args); } /*** * 直接用字符串生成一个条件对象 - * @param str sql条件 + * + * @param str + * sql条件 * @return 条件对象 */ public static Condition wrap(String str) { - return Strings.isBlank(str) ? null : new SimpleCondition((Object) str); + return Strings.isBlank(str) ? null : new SimpleCondition(str); } /** * 使用CharSegment拼装一个条件对象 - * @param sql sql模板 - * @param value 参数 + * + * @param sql + * sql模板 + * @param value + * 参数 * @return 条件对象 * @see org.nutz.lang.segment.CharSegment */ public static Condition wrap(String sql, Object value) { return Strings.isBlank(sql) ? null - : new SimpleCondition(new CharSegment(sql).setBy(value)); + : new SimpleCondition(new CharSegment(sql).setBy(value)); } /** * 生成一个条件表达式 - * @param name Java属性或字段名称 - * @param op 操作符,可以是 = like 等等 - * @param value 参数值. + * + * @param name + * Java属性或字段名称 + * @param op + * 操作符,可以是 = like 等等 + * @param value + * 参数值. * @return 条件表达式 */ public static SqlExpression exp(String name, String op, Object value) { - if(value!=null && value instanceof Nesting){ - return NestExps.create(name, op, (Nesting) value); - } + if (value != null && value instanceof Nesting) { + return NestExps.create(name, op, (Nesting) value); + } return Exps.create(name, op, value); } + /** + * 生成一个条件表达式 + * + * @param name + * lambda表达式 + * @param op + * 操作符,可以是 = like 等等 + * @param value + * 参数值. + * @return 条件表达式 + */ + public static SqlExpression exp(PFun name, String op, Object value) { + if (value != null && value instanceof Nesting) { + return NestExps.create(name, op, (Nesting) value); + } + return Exps.create(name, op, value); + } + + + /** * 生成一个条件表达式组 - * @param name Java属性或字段名称 - * @param op 操作符,可以是 = like 等等 - * @param value 参数值. + * + * @param name + * Java属性或字段名称 + * @param op + * 操作符,可以是 = like 等等 + * @param value + * 参数值. * @return 条件表达式组 */ public static SqlExpressionGroup exps(String name, String op, Object value) { return exps(exp(name, op, value)); } + /** + * 生成一个条件表达式组 + * + * @param name + * lambda表达式 + * @param op + * 操作符,可以是 = like 等等 + * @param value + * 参数值. + * @return 条件表达式组 + */ + public static SqlExpressionGroup exps(PFun name, String op, Object value) { + return exps(exp(name, op, value)); + } + /** * 将一个条件表达式封装为条件表达式组 - * @param exp 原本的条件表达式 + * + * @param exp + * 原本的条件表达式 * @return 条件表达式组 */ public static SqlExpressionGroup exps(SqlExpression exp) { @@ -137,18 +200,39 @@ public static SqlExpressionGroup exps(SqlExpression exp) { /** * 生成一个新的Cnd实例 - * @param name java属性或字段名称, 推荐用Java属性 - * @param op 操作符,可以是= like等等 - * @param value 参数值. 如果操作符是between,参数值需要是new Object[]{12,39}形式 + * + * @param name + * java属性或字段名称, 推荐用Java属性 + * @param op + * 操作符,可以是= like等等 + * @param value + * 参数值. 如果操作符是between,参数值需要是new Object[]{12,39}形式 * @return Cnd实例 */ public static Cnd where(String name, String op, Object value) { return new Cnd(Cnd.exp(name, op, value)); } + /** + * 生成一个新的Cnd实例 + * + * @param name + * lambda表达式 + * @param op + * 操作符,可以是= like等等 + * @param value + * 参数值. 如果操作符是between,参数值需要是new Object[]{12,39}形式 + * @return Cnd实例 + */ + public static Cnd where(PFun name, String op, Object value) { + return new Cnd(Cnd.exp(name, op, value)); + } + /** * 用一个条件表达式构建一个Cnd实例 - * @param e 条件表达式 + * + * @param e + * 条件表达式 * @return Cnd实例 */ public static Cnd where(SqlExpression e) { @@ -164,6 +248,7 @@ public static SimpleCriteria cri() { /** * 单纯生成一个Orderby条件 + * * @return OrderBy实例 */ public static OrderBy orderBy() { @@ -174,6 +259,7 @@ public static OrderBy orderBy() { * @return 一个 Cnd 的实例 * @deprecated Since 1.b.50 不推荐使用这个函数构建 Cnd 的实例,因为看起来语意不明的样子 */ + @Deprecated public static Cnd limit() { return new Cnd(); } @@ -187,7 +273,9 @@ public static Cnd NEW() { /** * 用SimpleCriteria生成一个Cnd实例 - * @param cri SimpleCriteria实例 + * + * @param cri + * SimpleCriteria实例 * @return Cnd实例 */ public static Cnd byCri(SimpleCriteria cri) { @@ -196,9 +284,9 @@ public static Cnd byCri(SimpleCriteria cri) { /*------------------------------------------------------------------*/ - private SimpleCriteria cri; + protected SimpleCriteria cri; - Cnd() { + protected Cnd() { cri = new SimpleCriteria(); } @@ -209,6 +297,7 @@ private Cnd setCri(SimpleCriteria cri) { /** * 获取内部的where属性 + * * @return SimpleCriteria实例 */ public SimpleCriteria getCri() { @@ -221,29 +310,61 @@ protected Cnd(SqlExpression exp) { } /** - * 按Java属性/字段属性进行升序. 不进行SQL特殊字符抹除 cnd.asc("age") - * @param name Java属性/字段属性 + * 按Java属性/字段属性进行升序. 不进行SQL特殊字符抹除 cnd.asc("age") + * + * @param name + * Java属性/字段属性 */ + @Override public OrderBy asc(String name) { cri.asc(name); return this; } - + + /** + * 按Java属性/字段属性进行升序. 不进行SQL特殊字符抹除 cnd.asc("age") + * + * @param name + * Lambda表达式 + */ + @Override + public OrderBy asc(PFun name) { + return asc(LambdaQuery.resolve(name)); + } + /** * 按Java属性/字段属性进行降序. 不进行SQL特殊字符抹除 cnd.desc("age") - * @param name Java属性/字段属性 + * + * @param name + * Java属性/字段属性 */ + @Override public OrderBy desc(String name) { cri.desc(name); return this; } + /** + * 按Java属性/字段属性进行降序. 不进行SQL特殊字符抹除 cnd.desc("age") + * + * @param name + * Lambda表达式 + */ + @Override + public OrderBy desc(PFun name) { + return desc(LambdaQuery.resolve(name)); + } + /** * 当dir为asc时判断为升序,否则判定为降序. cnd.orderBy("age", "asc") - * @param name Java属性/字段属性 - * @param dir asc或其他 + * + * @param name + * Java属性/字段属性 + * @param dir + * asc或其他 * @return OrderBy实例,事实上就是当前对象 */ + @Override public OrderBy orderBy(String name, String dir) { if ("asc".equalsIgnoreCase(dir)) { this.asc(name); @@ -254,8 +375,25 @@ public OrderBy orderBy(String name, String dir) { } /** - * Cnd.where(...).and(Cnd.exp(.........)) 或 Cnd.where(...).and(Cnd.exps(.........)) - * @param exp 条件表达式 + * 当dir为asc时判断为升序,否则判定为降序. cnd.orderBy("age", "asc") + * + * @param name + * Lambda表达式 + * @param dir + * asc或其他 + * @return OrderBy实例,事实上就是当前对象 + */ + @Override + public OrderBy orderBy(PFun name, String dir) { + return orderBy(LambdaQuery.resolve(name),dir); + } + + /** + * Cnd.where(...).and(Cnd.exp(.........)) 或 + * Cnd.where(...).and(Cnd.exps(.........)) + * + * @param exp + * 条件表达式 * @return 当前对象,用于链式调用 */ public Cnd and(SqlExpression exp) { @@ -265,18 +403,41 @@ public Cnd and(SqlExpression exp) { /** * Cnd.where(...).and("age", "<", 40) - * @param name Java属性或字段名称,推荐用Java属性,如果有的话 - * @param op 操作符,可以是 = like等 - * @param value 参数值, 如果是between的话需要传入new Object[]{19,28} + * + * @param name + * Java属性或字段名称,推荐用Java属性,如果有的话 + * @param op + * 操作符,可以是 = like等 + * @param value + * 参数值, 如果是between的话需要传入new Object[]{19,28} * @return 当前对象,用于链式调用 */ public Cnd and(String name, String op, Object value) { return and(Cnd.exp(name, op, value)); } + /** - * Cnd.where(...).or(Cnd.exp(.........)) 或 Cnd.where(...).or(Cnd.exps(.........)) - * @param exp 条件表达式 + * Cnd.where(...).and("age", "<", 40) + * + * @param name + * Lambda表达式 + * @param op + * 操作符,可以是 = like等 + * @param value + * 参数值, 如果是between的话需要传入new Object[]{19,28} + * @return 当前对象,用于链式调用 + */ + public Cnd and(PFun name, String op, Object value) { + return and(Cnd.exp(name, op, value)); + } + + /** + * Cnd.where(...).or(Cnd.exp(.........)) 或 + * Cnd.where(...).or(Cnd.exps(.........)) + * + * @param exp + * 条件表达式 * @return 当前对象,用于链式调用 */ public Cnd or(SqlExpression exp) { @@ -286,18 +447,39 @@ public Cnd or(SqlExpression exp) { /** * Cnd.where(...).or("age", "<", 40) - * @param name Java属性或字段名称,推荐用Java属性,如果有的话 - * @param op 操作符,可以是 = like等 - * @param value 参数值, 如果是between的话需要传入new Object[]{19,28} + * + * @param name + * Java属性或字段名称,推荐用Java属性,如果有的话 + * @param op + * 操作符,可以是 = like等 + * @param value + * 参数值, 如果是between的话需要传入new Object[]{19,28} * @return 当前对象,用于链式调用 */ public Cnd or(String name, String op, Object value) { return or(Cnd.exp(name, op, value)); } + /** + * Cnd.where(...).or("age", "<", 40) + * + * @param name + * Java属性或字段名称,推荐用Java属性,如果有的话 + * @param op + * 操作符,可以是 = like等 + * @param value + * 参数值, 如果是between的话需要传入new Object[]{19,28} + * @return 当前对象,用于链式调用 + */ + public Cnd or(PFun name, String op, Object value) { + return or(Cnd.exp(name, op, value)); + } + /** * and一个条件表达式并且取非 - * @param exp 条件表达式 + * + * @param exp + * 条件表达式 * @return 当前对象,用于链式调用 */ public Cnd andNot(SqlExpression exp) { @@ -307,15 +489,34 @@ public Cnd andNot(SqlExpression exp) { /** * and一个条件,并且取非 - * @param name Java属性或字段名称,推荐用Java属性,如果有的话 - * @param op 操作符,可以是 = like等 - * @param value 参数值, 如果是between的话需要传入new Object[]{19,28} + * + * @param name + * Java属性或字段名称,推荐用Java属性,如果有的话 + * @param op + * 操作符,可以是 = like等 + * @param value + * 参数值, 如果是between的话需要传入new Object[]{19,28} * @return 当前对象,用于链式调用 */ public Cnd andNot(String name, String op, Object value) { return andNot(Cnd.exp(name, op, value)); } + /** + * and一个条件,并且取非 + * + * @param name + * Lambda表达式 + * @param op + * 操作符,可以是 = like等 + * @param value + * 参数值, 如果是between的话需要传入new Object[]{19,28} + * @return 当前对象,用于链式调用 + */ + public Cnd andNot(PFun name, String op, Object value) { + return andNot(Cnd.exp(name, op, value)); + } + /** * @see Cnd#andNot(SqlExpression) */ @@ -331,9 +532,17 @@ public Cnd orNot(String name, String op, Object value) { return orNot(Cnd.exp(name, op, value)); } + /** + * @see Cnd#andNot(String, String, Object) + */ + public Cnd orNot(PFun name, String op, Object value) { + return orNot(Cnd.exp(name, op, value)); + } + /** * 获取分页对象,默认是null */ + @Override public Pager getPager() { return cri.getPager(); } @@ -341,6 +550,7 @@ public Pager getPager() { /** * 根据实体Entity将本对象转化为sql语句, 条件表达式中的name属性将转化为数据库字段名称 */ + @Override public String toSql(Entity en) { return cri.toSql(en); } @@ -348,6 +558,7 @@ public String toSql(Entity en) { /** * 判断两个Cnd是否相等 */ + @Override public boolean equals(Object obj) { return cri.equals(obj); } @@ -355,6 +566,7 @@ public boolean equals(Object obj) { /** * 直接转为SQL语句, 如果setPojo未曾调用, 条件表达式中的name属性未映射为数据库字段 */ + @Override public String toString() { return cri.toString(); } @@ -362,6 +574,7 @@ public String toString() { /** * 关联的Pojo,可以用于toString时的name属性映射 */ + @Override public void setPojo(Pojo pojo) { cri.setPojo(pojo); } @@ -369,22 +582,27 @@ public void setPojo(Pojo pojo) { /** * 获取已设置的Pojo, 默认为null */ + @Override public Pojo getPojo() { return cri.getPojo(); } + @Override public void joinSql(Entity en, StringBuilder sb) { cri.joinSql(en, sb); } + @Override public int joinAdaptor(Entity en, ValueAdaptor[] adaptors, int off) { return cri.joinAdaptor(en, adaptors, off); } + @Override public int joinParams(Entity en, Object obj, Object[] params, int off) { return cri.joinParams(en, obj, params, off); } + @Override public int paramCount(Entity en) { return cri.paramCount(en); } @@ -392,23 +610,42 @@ public int paramCount(Entity en) { /** * 获取Cnd中的where部分,注意,对SqlExpressionGroup的修改也会反映到Cnd中,因为是同一个对象 */ + @Override public SqlExpressionGroup where() { return cri.where(); } /** * 分组 - * @param names java属性或数据库字段名称 + * + * @param names + * java属性或数据库字段名称 */ + @Override public GroupBy groupBy(String... names) { cri.groupBy(names); return this; } + /** + * 分组 + * + * @param names + * Lambda表达式 + */ + @Override + public GroupBy groupBy(PFun... names) { + cri.groupBy(names); + return this; + } + /** * 分组中的having条件 - * @param cnd 条件语句 + * + * @param cnd + * 条件语句 */ + @Override public GroupBy having(Condition cnd) { cri.having(cnd); return this; @@ -417,14 +654,18 @@ public GroupBy having(Condition cnd) { /** * 单独获取排序条件,建议使用asc或desc,而非直接取出排序条件. 取出的对象仅包含分组条件, 不包含where等部分 */ + @Override public OrderBy getOrderBy() { return cri.getOrderBy(); } /** * 分页 - * @param pageNumber 页数, 若小于1则代表全部记录 - * @param pageSize 每页数量 + * + * @param pageNumber + * 页数, 若小于1则代表全部记录 + * @param pageSize + * 每页数量 * @return 当前对象,用于链式调用 */ public Cnd limit(int pageNumber, int pageSize) { @@ -434,7 +675,9 @@ public Cnd limit(int pageNumber, int pageSize) { /** * 设置每页大小,并设置页数为1 - * @param pageSize 每页大小 + * + * @param pageSize + * 每页大小 * @return 当前对象,用于链式调用 */ @Deprecated @@ -445,37 +688,50 @@ public Cnd limit(int pageSize) { /** * 直接设置分页对象, 可以new Pager或dao.createPager得到 - * @param pager 分页对象 + * + * @param pager + * 分页对象 * @return 当前对象,用于链式调用 */ public Cnd limit(Pager pager) { cri.setPager(pager); return this; } - + protected static FieldMatcher dftFromFieldMatcher = new FieldMatcher().setIgnoreNull(true).setIgnoreZero(true); - + /** * 用默认规则(忽略零值和空值)生成Cnd实例 - * @param dao Dao实例,不能为null - * @param obj 对象, 若为null,则返回值为null, 不可以是Class/字符串/数值/布尔类型 + * + * @param dao + * Dao实例,不能为null + * @param obj + * 对象, 若为null,则返回值为null, 不可以是Class/字符串/数值/布尔类型 * @return Cnd实例 */ public static Cnd from(Dao dao, Object obj) { return from(dao, obj, dftFromFieldMatcher); } - + /** - * 根据一个对象生成Cnd条件, FieldMatcher详细控制.

+ * 根据一个对象生成Cnd条件, FieldMatcher详细控制. + *

* assertEquals(" WHERE name='wendal' AND age=0", Cnd.from(dao, pet, FieldMatcher.make("age|name", null, true).setIgnoreDate(true)).toString()); - * @param dao Dao实例 - * @param obj 基对象,不可以是Class,字符串,数值和Boolean - * @param matcher 过滤字段属性, 可配置哪些字段可用/不可用/是否忽略空值/是否忽略0值/是否忽略java.util.Date类及其子类的对象/是否忽略@Id所标注的主键属性/是否忽略 \@Name 所标注的主键属性/是否忽略 \@Pk 所引用的复合主键 + * + * @param dao + * Dao实例 + * @param obj + * 基对象,不可以是Class,字符串,数值和Boolean + * @param matcher + * 过滤字段属性, + * 可配置哪些字段可用/不可用/是否忽略空值/是否忽略0值/是否忽略java.util.Date类及其子类的对象/是否忽略@Id所标注的主键属性/是否忽略 + * \@Name 所标注的主键属性/是否忽略 \@Pk 所引用的复合主键 * @return Cnd条件 */ public static Cnd from(Dao dao, Object obj, FieldMatcher matcher) { final SqlExpressionGroup exps = new SqlExpressionGroup(); boolean re = Daos.filterFields(obj, matcher, dao, new Callback2() { + @Override public void invoke(MappingField mf, Object val) { exps.and(mf.getName(), "=", val); } @@ -484,45 +740,117 @@ public void invoke(MappingField mf, Object val) { return Cnd.where(exps); return null; } - + /** * 若value为null/空白字符串/空集合/空数组,则本条件不添加. + * * @see Cnd#and(String, String, Object) */ public Cnd andEX(String name, String op, Object value) { return and(Cnd.expEX(name, op, value)); } - + + /** + * 若value为null/空白字符串/空集合/空数组,则本条件不添加. + * + * @see Cnd#and(String, String, Object) + */ + public Cnd andEX(PFun name, String op, Object value) { + return and(Cnd.expEX(name, op, value)); + } + /** * 若value为null/空白字符串/空集合/空数组,则本条件不添加. + * * @see Cnd#or(String, String, Object) */ public Cnd orEX(String name, String op, Object value) { return or(Cnd.expEX(name, op, value)); } - + + /** + * 若value为null/空白字符串/空集合/空数组,则本条件不添加. + * + * @see Cnd#or(String, String, Object) + */ + public Cnd orEX(PFun name, String op, Object value) { + return or(Cnd.expEX(name, op, value)); + } + public static SqlExpression expEX(String name, String op, Object value) { if (_ex(value)) return null; return Cnd.exp(name, op, value); } + public static SqlExpression expEX(PFun name, String op, Object value) { + return expEX(LambdaQuery.resolve(name),op,value); + } + + public static SqlExpression leftLikeEX(String name, Object value) { + if (_ex(value)) + return null; + return Cnd.exp(name, "like", String.format("%%%s", value)); + } + + public static SqlExpression leftLikeEX(PFun name, Object value) { + return leftLikeEX(LambdaQuery.resolve(name), value); + } + + public static SqlExpression rightLikeEX(String name, Object value) { + if (_ex(value)) + return null; + return Cnd.exp(name, "like", String.format("%s%%", value)); + } + + public static SqlExpression rightLikeEX(PFun name, Object value) { + return rightLikeEX(LambdaQuery.resolve(name), value); + } + + public static SqlExpression likeEX(String name, Object value) { + if (_ex(value)) + return null; + return Cnd.exp(name, "like", String.format("%%%s%%", value)); + } + + public static SqlExpression likeEX(PFun name, Object value) { + return likeEX(LambdaQuery.resolve(name),value); + } + @SuppressWarnings("rawtypes") public static boolean _ex(Object value) { return value == null - || (value instanceof CharSequence && Strings.isBlank((CharSequence)value)) - || (value instanceof Collection && ((Collection)value).isEmpty()) - || (value.getClass().isArray() && Array.getLength(value) == 0); + || (value instanceof CharSequence && Strings.isBlank((CharSequence) value)) + || (value instanceof Collection && ((Collection) value).isEmpty()) + || (value.getClass().isArray() && Array.getLength(value) == 0); } - + + @Override public GroupBy getGroupBy() { return cri.getGroupBy(); } - + /** * 构造一个可嵌套条件,需要dao支持才能映射类与表和属性与列 */ - public static Nesting nst(Dao dao){ - return new SimpleNesting(dao); + public static Nesting nst(Dao dao) { + return new SimpleNesting(dao); + } + + /** + * 克隆当前Cnd实例 + * + * @return 一模一样的兄弟 + */ + @Override + public Cnd clone() { + return Lang.fromBytes(Lang.toBytes(this), Cnd.class); + } + + /** + * 仅拷贝where条件, 不拷贝排序/分组/分页 + */ + public Cnd cloneWhere() { + return Cnd.where(this.cri.where().clone()); } } diff --git a/src/org/nutz/dao/DB.java b/src/org/nutz/dao/DB.java index 65b426dceb..bff7269883 100644 --- a/src/org/nutz/dao/DB.java +++ b/src/org/nutz/dao/DB.java @@ -55,6 +55,22 @@ public enum DB { * DM */ DM, + /** + * DM_MYSQL + */ + DM_MYSQL, + /** + * YashanDB + */ + YASHAN, + /** + * TDengine + */ + TDENGINE, + /** + * Kingbase + */ + KINGBASE, /** * 其他数据库 */ diff --git a/src/org/nutz/dao/Dao.java b/src/org/nutz/dao/Dao.java index 8bc9fbadbf..ec468d90fe 100644 --- a/src/org/nutz/dao/Dao.java +++ b/src/org/nutz/dao/Dao.java @@ -1,5 +1,9 @@ package org.nutz.dao; +import java.sql.ResultSet; +import java.util.List; +import java.util.Map; + import org.nutz.dao.entity.Entity; import org.nutz.dao.entity.Record; import org.nutz.dao.impl.EntityHolder; @@ -7,17 +11,15 @@ import org.nutz.dao.pager.Pager; import org.nutz.dao.sql.PojoMaker; import org.nutz.dao.sql.Sql; +import org.nutz.lang.Configurable; import org.nutz.lang.Each; -import java.sql.ResultSet; -import java.util.List; - /** * Nutz.Dao 核心接口。 封装了所有的数据库操作 * * @author zozoh(zozohtnt@gmail.com) */ -public interface Dao { +public interface Dao extends Configurable { /** * @return 数据源的元数据 @@ -106,6 +108,8 @@ public interface Dao { */ T insert(T obj); + T insert(Entity entity, T obj); + /** * 将一个对象按FieldFilter过滤后,插入到一个数据源。 *

@@ -119,7 +123,7 @@ public interface Dao { * @see org.nutz.dao.Dao#insert(Object) */ T insert(T obj, FieldFilter filter); - + T insert(T obj, String actived); /** @@ -145,6 +149,8 @@ public interface Dao { */ void insert(Class classOfT, Chain chain); + void insert(Entity entity, Chain chain); + /** * 快速插入一个对象。 对象的 '@Prev' 以及 '@Next' 在这个函数里不起作用。 *

@@ -165,6 +171,10 @@ public interface Dao { */ T fastInsert(T obj); + T fastInsert(T obj, boolean detectAllColumns); + + void fastInsert(Entity entity, Object obj); + /** * 将对象插入数据库同时,也将符合一个正则表达式的所有关联字段关联的对象统统插入相应的数据库 *

@@ -267,6 +277,20 @@ public interface Dao { int update(Object obj, Condition cnd); + int update(Entity entity, Object obj); + + int update(Entity entity, Object obj, String actived); + + int update(Entity entity, Object obj, String actived, String locked, boolean ignoreNull); + + int update(Entity entity, Object obj, FieldFilter fieldFilter); + + int update(Entity entity, Object obj, FieldFilter fieldFilter, Condition cnd); + + int update(Entity entity, Object obj, Condition cnd); + + int update(Entity entity, Chain chain, Condition cnd); + /** * 更新一个对象,并且忽略所有 null 字段。 *

@@ -390,6 +414,8 @@ public interface Dao { */ List query(Class classOfT, Condition cnd, Pager pager); + List query(Entity entity, Condition cnd, Pager pager); + /** * 查询一组对象。你可以为这次查询设定条件 * @@ -469,6 +495,8 @@ public interface Dao { */ int each(Class classOfT, Condition cnd, Pager pager, Each callback); + int each(Entity entity, Condition cnd, Pager pager, Each callback); + /** * 对一组对象进行迭代,这个接口函数非常适用于很大的数据量的集合,因为你不可能把他们都读到内存里 * @@ -550,6 +578,8 @@ public interface Dao { */ int delete(Class classOfT, long id); + int delete(Entity entity, long id); + /** * 根据对象 Name 删除一个对象。它只会删除这个对象,关联对象不会被删除。 *

@@ -567,6 +597,8 @@ public interface Dao { */ int delete(Class classOfT, String name); + int delete(Entity entity, String name); + /** * 根据复合主键,删除一个对象。该对象必须声明 '@PK',并且,给定的参数顺序 必须同 '@PK' 中声明的顺序一致,否则会产生不可预知的错误。 * @@ -647,6 +679,8 @@ public interface Dao { */ T fetch(Class classOfT, long id); + T fetch(Entity entity, long id); + /** * 根据对象 Name 获取一个对象。它只会获取这个对象,关联对象不会被获取。 *

@@ -662,6 +696,8 @@ public interface Dao { */ T fetch(Class classOfT, String name); + T fetch(Entity entity, String name); + /** * 根据复合主键,获取一个对象。该对象必须声明 '@PK',并且,给定的参数顺序 必须同 '@PK' 中声明的顺序一致,否则会产生不可预知的错误。 * @@ -671,6 +707,8 @@ public interface Dao { */ T fetchx(Class classOfT, Object... pks); + T fetchx(Entity entity, Object... pks); + /** * 根据 WHERE 条件获取一个对象。如果有多个对象符合条件,将只获取 ResultSet 第一个记录 * @@ -685,6 +723,8 @@ public interface Dao { */ T fetch(Class classOfT, Condition cnd); + T fetch(Entity entity, Condition cnd); + /** * 根据条件获取一个 Record 对象 * @@ -773,6 +813,8 @@ public interface Dao { */ int clear(Class classOfT, Condition cnd); + int clear(Entity entity, Condition cnd); + /** * 根据一个 WHERE 条件,清除一组记录 * @@ -844,6 +886,8 @@ public interface Dao { */ int count(Class classOfT, Condition cnd); + int count(Entity entity, Condition cnd); + /** * 计算某个对象在数据库中有多少条记录 * @@ -853,6 +897,8 @@ public interface Dao { */ int count(Class classOfT); + int count(Entity entity); + /** * 根据条件,计算某个数据表或视图中有多少条记录 * @@ -893,7 +939,7 @@ public interface Dao { * @return 计算结果 */ int func(Class classOfT, String funcName, String fieldName); - + /** * 对某一个对象字段,进行计算。 * @@ -1011,6 +1057,8 @@ public interface Dao { */ boolean exists(Class classOfT); + boolean exists(Entity entity); + /** * @param tableName * 表名 @@ -1029,6 +1077,41 @@ public interface Dao { */ Entity create(Class classOfT, boolean dropIfExists); + /** + * 根据一个实体的配置信息为其创建一张表 + * + * @param en + * 实体类型,抽象后的 + * @param dropIfExists + * 如果表存在是否强制移除 + * @return 实体对象 + */ + Entity create(Entity en, boolean dropIfExists); + + /** + * 根据一个实体的配置信息为其创建一张表 + * + * @param tableName + * 表名 + * @param map + * 实体描述,参考文档中的非Pojo操作 + * @param dropIfExists + * 如果表存在是否强制移除 + * @return 实体对象 + */ + > Entity create(String tableName, T map, boolean dropIfExists); + + /** + * 根据一个实体的配置信息为其创建一张表, 其中表名从map.get(".table")获取 + * + * @param map + * 实体描述,参考文档中的非Pojo操作 + * @param dropIfExists + * 如果表存在是否强制移除 + * @return 实体对象 + */ + > Entity create(T map, boolean dropIfExists); + /** * 如果一个实体的数据表存在,移除它 * @@ -1096,7 +1179,7 @@ public interface Dao { * @return 原对象 */ T insertOrUpdate(T t); - + /** * 根据对象的主键(@Id/@Name/@Pk)先查询, 如果存在就更新, 不存在就插入 * @@ -1127,28 +1210,42 @@ public interface Dao { */ int updateAndIncrIfMatch(Object obj, FieldFilter fieldFilter, String fieldName); + int updateAndIncrIfMatch(Entity entity, + Object obj, + FieldFilter fieldFilter, + String fieldName); + /** * 基于版本的更新,版本不一样无法更新到数据 - * @param obj 需要更新的对象, 必须有version属性 + * + * @param obj + * 需要更新的对象, 必须有version属性 * @return 若更新成功,大于0, 否则小于0 */ int updateWithVersion(Object obj); - + /** * 基于版本的更新,版本不一样无法更新到数据 - * @param obj 需要更新的对象, 必须有version属性 - * @param fieldFilter 需要过滤的字段设置 + * + * @param obj + * 需要更新的对象, 必须有version属性 + * @param fieldFilter + * 需要过滤的字段设置 * @return 若更新成功,大于0, 否则小于0 */ int updateWithVersion(Object obj, FieldFilter fieldFilter); - + /** * 根据查询条件获取一个对象.注意: 条件语句需要加上表名!!! *

* 这个方法是让@One关联的属性,通过left join一次性取出. 与fetch+fetchLinks是等价的 - * @param classOfT 实体类 - * @param regex 需要过滤的关联属性,可以是null,取出全部关联属性. - * @param cnd 查询条件,必须带表名!!! + * + * @param classOfT + * 实体类 + * @param regex + * 需要过滤的关联属性,可以是null,取出全部关联属性. + * @param cnd + * 查询条件,必须带表名!!! * @return 实体对象,符合regex的关联属性也会取出 */ T fetchByJoin(Class classOfT, String regex, Condition cnd); @@ -1159,13 +1256,17 @@ public interface Dao { * 你的对象必须在某个字段声明了注解 '@Id',否则本操作会抛出一个运行时异常 *

* 这个方法是让@One关联的属性,通过left join一次性取出. 与fetch+fetchLinks是等价的 - * @param classOfT 实体类 - * @param regex 需要取出的关联属性,是正则表达式哦,匹配的是Java属性名 - * @param id 对象id + * + * @param classOfT + * 实体类 + * @param regex + * 需要取出的关联属性,是正则表达式哦,匹配的是Java属性名 + * @param id + * 对象id * @return 实体 */ T fetchByJoin(Class classOfT, String regex, long id); - + /** * 根据对象 NAME 获取一个对象。它只会获取这个对象,关联对象不会被获取。 *

@@ -1173,39 +1274,72 @@ public interface Dao { *

* 这个方法是让@One关联的属性,通过left join一次性取出. 与fetch+fetchLinks是等价的 * - * @param classOfT 实体类 - * @param regex 需要取出的关联属性,是正则表达式哦,匹配的是Java属性名 - * @param name 对象name + * @param classOfT + * 实体类 + * @param regex + * 需要取出的关联属性,是正则表达式哦,匹配的是Java属性名 + * @param name + * 对象name * @return 实体 */ T fetchByJoin(Class classOfT, String regex, String name); - + /** - * 根据查询条件获取所有对象.注意: 条件语句需要加上表名!!! + * 根据查询条件获取所有对象.注意: 条件语句需要加上主表名或关联属性的JAVA属性名!!! *

* 这个方法是让@One关联的属性,通过left join一次性取出. 与query+fetchLinks是等价的 - * @param classOfT 实体类 - * @param regex 需要过滤的关联属性,可以是null,取出全部关联属性. - * @param cnd 查询条件,必须带表名!!! + * + * @param classOfT + * 实体类 + * @param regex + * 需要过滤的关联属性,可以是null,取出全部关联属性. + * @param cnd + * 查询条件, 主表写表名, 子表写关联属性的JAVA属性名! * @return 实体对象的列表,符合regex的关联属性也会取出 */ List queryByJoin(Class classOfT, String regex, Condition cnd); - + + T fetchByJoin(Class classOfT, String regex, Condition cnd, Map cnds); + /** - * 根据查询条件获取分页对象.注意: 条件语句需要加上表名!!! + * 根据查询条件获取分页对象.注意: 条件语句需要加上主表名或关联属性的JAVA属性名!!! *

* 这个方法是让@One关联的属性,通过left join一次性取出. 与query+fetchLinks是等价的 - * @param classOfT 实体类 - * @param regex 需要过滤的关联属性,可以是null,取出全部关联属性. - * @param cnd 查询条件,必须带表名!!! - * @param pager 分页对象 注意: 分页不要在cnd中传入! + * + * @param classOfT + * 实体类 + * @param regex + * 需要过滤的关联属性,可以是null,取出全部关联属性. + * @param cnd + * 查询条件, 主表写表名, 子表写关联属性的JAVA属性名! + * @param pager + * 分页对象 注意: 分页不要在cnd中传入! * @return 实体对象的列表,符合regex的关联属性也会取出 */ List queryByJoin(Class classOfT, String regex, Condition cnd, Pager pager); - + + List queryByJoin(Class classOfT, + String regex, + Condition cnd, + Pager pager, + Map cnds); + + /** + * 根据查询条件获取分页对象.注意: 条件语句需要加上主表名或关联属性的JAVA属性名!!! + * + * @param classOfT + * 实体类 + * @param regex + * 需要过滤的关联属性,可以是null,取出全部关联属性. + * @param cnd + * 查询条件, 主表写表名, 子表写关联属性的JAVA属性名! + * @return 数量 + */ + int countByJoin(Class classOfT, String regex, Condition cnd); + EntityHolder getEntityHolder(); - + void truncate(Class klass); - + void truncate(String tableName); } diff --git a/src/org/nutz/dao/DaoInterceptorChain.java b/src/org/nutz/dao/DaoInterceptorChain.java index e7a974d9f2..d3b6cd5b47 100644 --- a/src/org/nutz/dao/DaoInterceptorChain.java +++ b/src/org/nutz/dao/DaoInterceptorChain.java @@ -48,7 +48,6 @@ public class DaoInterceptorChain implements ConnCallback { */ public DaoInterceptorChain(DaoStatement... sts) { this.sts = sts; - id = R.UU32(); } /** @@ -221,7 +220,10 @@ public SqlContext getSqlContext() { * * @return 本拦截器链的id */ - public String getId() { + @Deprecated + public synchronized String getId() { + if (id == null) + id = R.UU32(); return id; } diff --git a/src/org/nutz/dao/DatabaseMeta.java b/src/org/nutz/dao/DatabaseMeta.java index 4787aff017..1dabfe68af 100644 --- a/src/org/nutz/dao/DatabaseMeta.java +++ b/src/org/nutz/dao/DatabaseMeta.java @@ -41,7 +41,7 @@ public void setProductName(String productName) { type = DB.H2; } else if (proName.startsWith("postgresql")) { type = DB.PSQL; - } else if (proName.startsWith("mysql")) { + } else if (proName.startsWith("mysql") || proName.startsWith("mariadb")) { type = DB.MYSQL; } else if (proName.startsWith("oracle")) { type = DB.ORACLE; @@ -55,12 +55,20 @@ public void setProductName(String productName) { type = DB.HSQL; } else if (proName.contains("derby")) { type = DB.DERBY; + } else if (proName.contains("kingbase")) { + type = DB.KINGBASE; } else if (proName.contains("gbase")) { type = DB.GBASE; } else if (proName.contains("sysbase")) { type = DB.SYBASE; } else if (proName.contains("dm dbms")) { type = DB.DM; + } else if (proName.contains("dm mysql")) { + type = DB.DM_MYSQL; + } else if (proName.contains("yashandb")) { + type = DB.YASHAN; + } else if (proName.contains("tdengine")) { + type = DB.TDENGINE; } else { type = DB.OTHER; } @@ -159,8 +167,12 @@ public boolean isHsql() { public boolean isDerby() { return DB.DERBY == type; } - + public boolean isDm() { return DB.DM == type; } + + public boolean isYashan() { + return DB.YASHAN == type; + } } diff --git a/src/org/nutz/dao/FieldMatcher.java b/src/org/nutz/dao/FieldMatcher.java index 1b9300c518..36ef4838a1 100644 --- a/src/org/nutz/dao/FieldMatcher.java +++ b/src/org/nutz/dao/FieldMatcher.java @@ -1,11 +1,14 @@ package org.nutz.dao; import java.util.Arrays; +import java.util.Date; import java.util.HashSet; import java.util.Set; import java.util.regex.Pattern; +import org.nutz.dao.entity.MappingField; import org.nutz.lang.Strings; +import org.nutz.lang.util.Regex; /** * 字段匹配器. 判断顺序 locked--actived-->ignoreNull. @@ -28,9 +31,9 @@ public static FieldMatcher make(String actived, String locked, boolean ignoreNul FieldMatcher fm = new FieldMatcher(); fm.ignoreNull = ignoreNull; if (!Strings.isBlank(actived)) - fm.actived = Pattern.compile(actived); + fm.actived = Regex.getPattern(actived); if (!Strings.isBlank(locked)) - fm.locked = Pattern.compile(locked); + fm.locked = Regex.getPattern(locked); return fm; } @@ -110,31 +113,33 @@ public static FieldMatcher make(String actived, String locked, boolean ignoreNul /** * 是否忽略空值 */ - private boolean ignoreNull = true; + private Boolean ignoreNull = true; /** * 是否忽略空白字符串 */ - private boolean ignoreBlankStr; + private Boolean ignoreBlankStr; /** * 是否忽略零值 */ - private boolean ignoreZero = true; + private Boolean ignoreZero; /** * 是否忽略日期 */ - private boolean ignoreDate; + private Boolean ignoreDate; /** * 是否忽略@Id标注的属性 */ - private boolean ignoreId = true; + private Boolean ignoreId = true; /** * 是否忽略@Name标注的属性 */ - private boolean ignoreName; + private Boolean ignoreName; /** * 是否忽略@Pk标注引用的属性 */ - private boolean ignorePk; + private Boolean ignorePk; + + private Boolean ignoreFalse; /** * 匹配顺序 locked -- actived-- ignoreNull @@ -150,6 +155,44 @@ public boolean match(String str) { } return true; } + + public boolean match(MappingField mf, Object obj) { + String fieldName = mf.getName(); + if (null != locked && locked.matcher(fieldName).find()) { + return false; + } + if (null != actived && !actived.matcher(fieldName).find()) { + return false; + } + if (ignoreId != null && ignoreId && mf.isId()) + return false; + if (ignoreName != null && ignoreName && mf.isName()) + return false; + if (ignorePk != null && ignorePk && mf.isCompositePk()) + return false; + Object val = mf.getValue(obj); + if (val == null) { + if (ignoreNull != null && ignoreNull) + return false; + } else { + if (ignoreZero != null && ignoreZero + && val instanceof Number + && ((Number) val).doubleValue() == 0.0) { + return false; + } + if (ignoreDate != null && ignoreDate && val instanceof Date) { + return false; + } + if (ignoreBlankStr != null && ignoreBlankStr + && val instanceof CharSequence + && Strings.isBlank((CharSequence) val)) { + return false; + } + if (val instanceof Boolean && ignoreFalse != null && ignoreFalse && !((Boolean)val)) + return false; + } + return true; + } /** * 是否忽略控制 @@ -192,7 +235,7 @@ public Pattern getLocked() { */ public FieldMatcher setActived(String actived) { if (actived != null) - this.actived = Pattern.compile(actived); + this.actived = Regex.getPattern(actived); else this.actived = null; return this; @@ -205,7 +248,7 @@ public FieldMatcher setActived(String actived) { */ public FieldMatcher setLocked(String locked) { if (locked != null) - this.locked = Pattern.compile(locked); + this.locked = Regex.getPattern(locked); else this.locked = null; return this; @@ -302,13 +345,17 @@ public FieldMatcher setIgnorePk(boolean ignorePk) { } public boolean isIgnoreBlankStr() { - return ignoreBlankStr; + return ignoreBlankStr != null && ignoreBlankStr; } public FieldMatcher setIgnoreBlankStr(boolean ignoreBlankStr) { this.ignoreBlankStr = ignoreBlankStr; return this; } + + public void setIgnoreFalse(Boolean ignoreFalse) { + this.ignoreFalse = ignoreFalse; + } public static FieldMatcher simple(String ...fields) { final Set m = new HashSet(Arrays.asList(fields)); @@ -316,6 +363,9 @@ public static FieldMatcher simple(String ...fields) { public boolean match(String str) { return m.contains(str); } + public boolean match(MappingField mf, Object obj) { + return this.match(mf.getName()); + } }; } } diff --git a/src/org/nutz/dao/SqlManager.java b/src/org/nutz/dao/SqlManager.java index 9d12e2b23b..552ea63754 100644 --- a/src/org/nutz/dao/SqlManager.java +++ b/src/org/nutz/dao/SqlManager.java @@ -78,4 +78,5 @@ public interface SqlManager { */ void remove(String key); + void clear(); } diff --git a/src/org/nutz/dao/Sqls.java b/src/org/nutz/dao/Sqls.java index 01295a887a..e8885add49 100644 --- a/src/org/nutz/dao/Sqls.java +++ b/src/org/nutz/dao/Sqls.java @@ -3,7 +3,25 @@ import org.nutz.castor.Castors; import org.nutz.dao.impl.sql.NutSql; import org.nutz.dao.impl.sql.ValueEscaper; -import org.nutz.dao.impl.sql.callback.*; +import org.nutz.dao.impl.sql.callback.FetchBlobCallback; +import org.nutz.dao.impl.sql.callback.FetchBooleanCallback; +import org.nutz.dao.impl.sql.callback.FetchDoubleCallback; +import org.nutz.dao.impl.sql.callback.FetchEntityCallback; +import org.nutz.dao.impl.sql.callback.FetchFloatCallback; +import org.nutz.dao.impl.sql.callback.FetchIntegerCallback; +import org.nutz.dao.impl.sql.callback.FetchLongCallback; +import org.nutz.dao.impl.sql.callback.FetchMapCallback; +import org.nutz.dao.impl.sql.callback.FetchRecordCallback; +import org.nutz.dao.impl.sql.callback.FetchStringCallback; +import org.nutz.dao.impl.sql.callback.FetchTimestampCallback; +import org.nutz.dao.impl.sql.callback.QueryBooleanCallback; +import org.nutz.dao.impl.sql.callback.QueryEntityCallback; +import org.nutz.dao.impl.sql.callback.QueryIntCallback; +import org.nutz.dao.impl.sql.callback.QueryLongCallback; +import org.nutz.dao.impl.sql.callback.QueryMapCallback; +import org.nutz.dao.impl.sql.callback.QueryRecordCallback; +import org.nutz.dao.impl.sql.callback.QueryStringArrayCallback; +import org.nutz.dao.impl.sql.callback.QueryStringCallback; import org.nutz.dao.sql.Sql; import org.nutz.dao.sql.SqlCallback; import org.nutz.lang.Mirror; @@ -349,6 +367,13 @@ public SqlCallback map() { public SqlCallback maps() { return QueryMapCallback.me; } + + /** + * @return 从 ResultSet 获得一个blob的回调对象 + */ + public SqlCallback blob() { + return new FetchBlobCallback(); + } } /** diff --git a/src/org/nutz/dao/TableName.java b/src/org/nutz/dao/TableName.java index a788e31cb9..5a5e3d1fce 100644 --- a/src/org/nutz/dao/TableName.java +++ b/src/org/nutz/dao/TableName.java @@ -8,6 +8,7 @@ import org.nutz.lang.util.Context; import org.nutz.log.Log; import org.nutz.log.Logs; +import org.nutz.trans.Proton; /** * 将一个参考对象存入 ThreadLocal @@ -51,6 +52,36 @@ public static void run(Object refer, Runnable atom) { } } + /** + * 代码模板,这个模板保证了,在 atom 中运行的 POJO 的动态表名,都会被参考对象所影响 + * + * @param refer + * 参考对象 + * @param proton + * 你的业务逻辑(可带返回值) + * + * @return 你的业务逻辑的返回对象 + */ + public static T run(Object refer, Proton proton) { + if (log.isTraceEnabled()) + log.tracef("TableName.run: [%s]->[%s]", object, object.get()); + + Object old = get(); + set(refer); + try { + proton.run(); + return proton.get(); + } + catch (Exception e) { + throw Lang.wrapThrow(e); + } + finally { + set(old); + if (log.isTraceEnabled()) + log.tracef("TableName.finally: [%s]->[%s]", object, object.get()); + } + } + /** * @return 当前线程中的动态表名参考对象 */ diff --git a/src/org/nutz/dao/entity/Entity.java b/src/org/nutz/dao/entity/Entity.java index 5633783461..6ad5e381f4 100644 --- a/src/org/nutz/dao/entity/Entity.java +++ b/src/org/nutz/dao/entity/Entity.java @@ -1,6 +1,7 @@ package org.nutz.dao.entity; import org.nutz.dao.FieldMatcher; +import org.nutz.dao.interceptor.PojoInterceptor; import org.nutz.dao.sql.Pojo; import org.nutz.lang.Mirror; import org.nutz.lang.util.Context; @@ -284,4 +285,10 @@ public interface Entity { * @return 实体version字段映射 */ MappingField getVersionField(); + + PojoInterceptor getInterceptor(); + + void setInterceptor(PojoInterceptor interceptor); + + boolean hasInsertMacroes(); } diff --git a/src/org/nutz/dao/entity/EntityField.java b/src/org/nutz/dao/entity/EntityField.java index 31b6253d65..18e9904dd2 100644 --- a/src/org/nutz/dao/entity/EntityField.java +++ b/src/org/nutz/dao/entity/EntityField.java @@ -24,7 +24,7 @@ public interface EntityField { /** * @return 获取该字段 Java 对象的类型 */ - Mirror getTypeMirror(); + Mirror getMirror(); /** * 为当前实体字段注入值,优先通过 setter 注入 diff --git a/src/org/nutz/dao/entity/MappingField.java b/src/org/nutz/dao/entity/MappingField.java index 83d4bc3a52..d9cf8d5b1c 100644 --- a/src/org/nutz/dao/entity/MappingField.java +++ b/src/org/nutz/dao/entity/MappingField.java @@ -12,173 +12,182 @@ */ public interface MappingField extends EntityField { - /** - * 通过 Record 为映射字段注入值 - * - * @param obj - * 被注入对象 - * @param rec - * 结果集 - * @param prefix TODO - */ - void injectValue(Object obj, Record rec, String prefix); - - /** - * 通过 resultSet 为映射字段注入值 - * - * @param obj - * 被注入对象 - * @param rs - * 结果集 - * @param prefix TODO - */ - void injectValue(Object obj, ResultSet rs, String prefix); - - /** - * @return 字段值适配器 - */ - ValueAdaptor getAdaptor(); - - /** - * 设置字段值适配器 - * - * @param adaptor - * 字段值适配器 - */ - void setAdaptor(ValueAdaptor adaptor); - - /** - * @return 数据库中的字段名 - */ - String getColumnName(); - - String getColumnNameInSql(); - - /** - * @return 数据库中字段的注释 - */ - String getColumnComment(); - - /** - * @return 数据库中的字段类型 - */ - ColType getColumnType(); - - /** - * 设置字段在数据库中的类型 - * - * @param colType - * 数据库字段的类型 - */ - void setColumnType(ColType colType); - - /** - * 根据实体的实例对象,获取默认值 - * - * @param obj - * 当前实体的实例对象 - * - * @return 数据库字段的默认值 - * - * @see org.nutz.dao.entity.annotation.Default - */ - String getDefaultValue(Object obj); - - /** - * @return 字段宽度。默认 0 表示自动决定 - */ - int getWidth(); - - /** - * @return 字段的精度,仅浮点有效。默认 2 - */ - int getPrecision(); - - /** - * @return 当前字段是否是主键(包括复合主键) - */ - boolean isPk(); - - /** - * @return 当前字段是否是复合主键 - */ - boolean isCompositePk(); - - /** - * @return 当前字段是否是数字型主键 - */ - boolean isId(); - - /** - * @return 当前字段是否是字符型主键 - */ - boolean isName(); - - /** - * @return 当前字段是否是只读 - */ - boolean isReadonly(); - - /** - * 将字段设置成只读 - */ - void setAsReadonly(); - - /** - * @return 字段是否设置了默认值 - */ - boolean hasDefaultValue(); - - /** - * @return 当前字段有非空约束 - */ - boolean isNotNull(); - - /** - * @return 是否为无符号 - */ - boolean isUnsigned(); - - /** - * @return 当前字段是否大小写敏感 - */ - boolean isCasesensitive(); - - /** - * 将字段设置成非空约束 - */ - void setAsNotNull(); - - /** - * 这个判断仅仅对于创建语句有作用。 - * - * @return 当前字段是否是自增的 - */ - boolean isAutoIncreasement(); - - /** - * @return 当前字段是否有注释。 - */ - boolean hasColumnComment(); - - void setCustomDbType(String customDbType); - - String getCustomDbType(); - - /** - * @return 当前字段是否参与保存操作 - */ - boolean isInsert(); - - /** - * @return 当前字段是否参与更新操作 - */ - boolean isUpdate(); - - - /** - * @return 当前字段是否version字段 - */ - boolean isVersion(); + /** + * 通过 Record 为映射字段注入值 + * + * @param obj + * 被注入对象 + * @param rec + * 结果集 + * @param prefix + * TODO + */ + void injectValue(Object obj, Record rec, String prefix); + + /** + * 通过 resultSet 为映射字段注入值 + * + * @param obj + * 被注入对象 + * @param rs + * 结果集 + * @param prefix + * TODO + */ + void injectValue(Object obj, ResultSet rs, String prefix); + + /** + * @return 字段值适配器 + */ + ValueAdaptor getAdaptor(); + + /** + * 设置字段值适配器 + * + * @param adaptor + * 字段值适配器 + */ + void setAdaptor(ValueAdaptor adaptor); + + /** + * @return 数据库中的字段名 + */ + String getColumnName(); + + String getColumnNameInSql(); + + /** + * @return 数据库中字段的注释 + */ + String getColumnComment(); + + /** + * @return 数据库中的字段类型 + */ + ColType getColumnType(); + + /** + * 设置字段在数据库中的类型 + * + * @param colType + * 数据库字段的类型 + */ + void setColumnType(ColType colType); + + /** + * 设置本字段关联的实体 + * + * @param entity + * 实体 + */ + void setEntity(Entity entity); + + /** + * 根据实体的实例对象,获取默认值 + * + * @param obj + * 当前实体的实例对象 + * + * @return 数据库字段的默认值 + * + * @see org.nutz.dao.entity.annotation.Default + */ + String getDefaultValue(Object obj); + + /** + * @return 字段宽度。默认 0 表示自动决定 + */ + int getWidth(); + + /** + * @return 字段的精度,仅浮点有效。默认 2 + */ + int getPrecision(); + + /** + * @return 当前字段是否是主键(包括复合主键) + */ + boolean isPk(); + + /** + * @return 当前字段是否是复合主键 + */ + boolean isCompositePk(); + + /** + * @return 当前字段是否是数字型主键 + */ + boolean isId(); + + /** + * @return 当前字段是否是字符型主键 + */ + boolean isName(); + + /** + * @return 当前字段是否是只读 + */ + boolean isReadonly(); + + /** + * 将字段设置成只读 + */ + void setAsReadonly(); + + /** + * @return 字段是否设置了默认值 + */ + boolean hasDefaultValue(); + + /** + * @return 当前字段有非空约束 + */ + boolean isNotNull(); + + /** + * @return 是否为无符号 + */ + boolean isUnsigned(); + + /** + * @return 当前字段是否大小写敏感 + */ + boolean isCasesensitive(); + + /** + * 将字段设置成非空约束 + */ + void setAsNotNull(); + + /** + * 这个判断仅仅对于创建语句有作用。 + * + * @return 当前字段是否是自增的 + */ + boolean isAutoIncreasement(); + + /** + * @return 当前字段是否有注释。 + */ + boolean hasColumnComment(); + + void setCustomDbType(String customDbType); + + String getCustomDbType(); + + /** + * @return 当前字段是否参与保存操作 + */ + boolean isInsert(); + + /** + * @return 当前字段是否参与更新操作 + */ + boolean isUpdate(); + + /** + * @return 当前字段是否version字段 + */ + boolean isVersion(); } diff --git a/src/org/nutz/dao/entity/Record.java b/src/org/nutz/dao/entity/Record.java index 3a62c8f946..fa80b9a490 100644 --- a/src/org/nutz/dao/entity/Record.java +++ b/src/org/nutz/dao/entity/Record.java @@ -1,51 +1,44 @@ package org.nutz.dao.entity; -import java.sql.ResultSet; -import java.sql.ResultSetMetaData; -import java.sql.SQLException; -import java.sql.Timestamp; -import java.sql.Types; -import java.util.ArrayList; -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.Callable; - import org.nutz.castor.Castors; import org.nutz.dao.Chain; import org.nutz.dao.DaoException; +import org.nutz.dao.impl.jdbc.BlobValueAdaptor; +import org.nutz.dao.jdbc.Jdbcs; import org.nutz.json.Json; import org.nutz.json.JsonFormat; import org.nutz.lang.Lang; import org.nutz.lang.util.NutMap; +import java.sql.*; +import java.util.*; +import java.util.concurrent.Callable; + /** * 记录对象 - * + * * @author zozoh(zozohtnt@gmail.com) * @author wendal(wendal1985@gmail.com) + * @author wizzercn(wizzer.cn@gmail.com) */ public class Record implements Map, java.io.Serializable, Cloneable, Comparable { private static final long serialVersionUID = -7753504263747912181L; - + protected static Callable factory; - + /** * 根据 ResultSet 创建一个记录对象 - * - * @param rs - * ResultSet 对象 + * + * @param rs ResultSet 对象 * @return 记录对象 */ public static Record create(ResultSet rs) { - Record re = create(); - create(re, rs, null); - return re; + Record re = create(); + create(re, rs, null); + return re; } - + public static void create(Map re, ResultSet rs, ResultSetMetaData meta) { String name = null; int i = 0; @@ -56,26 +49,29 @@ public static void create(Map re, ResultSet rs, ResultSetMetaDat for (i = 1; i <= count; i++) { name = meta.getColumnLabel(i); switch (meta.getColumnType(i)) { - case Types.TIMESTAMP: { - re.put(name, rs.getTimestamp(i)); - break; - } - case Types.DATE: {// ORACLE的DATE类型包含时间,如果用默认的只有日期没有时间 from - // cqyunqin - re.put(name, rs.getTimestamp(i)); - break; - } - case Types.CLOB: { - re.put(name, rs.getString(i)); - break; - } - default: - re.put(name, rs.getObject(i)); - break; + case Types.TIMESTAMP: { + re.put(name, rs.getTimestamp(i)); + break; + } + case Types.DATE: {// ORACLE的DATE类型包含时间,如果用默认的只有日期没有时间 from + // cqyunqin + re.put(name, rs.getTimestamp(i)); + break; + } + case Types.CLOB: { + re.put(name, rs.getString(i)); + break; + } + case Types.BLOB: { + re.put(name, new BlobValueAdaptor(Jdbcs.getFilePool()).get(rs, name)); + break; + } + default: + re.put(name, rs.getObject(i)); + break; } } - } - catch (SQLException e) { + } catch (SQLException e) { if (name != null) { throw new DaoException(String.format("Column Name=%s, index=%d", name, i), e); } @@ -93,11 +89,9 @@ public Record() { /** * 设置值 - * - * @param name - * 字段名 - * @param value - * 字段值 + * + * @param name 字段名 + * @param value 字段值 * @return 记录本身 */ public Record set(String name, Object value) { @@ -108,9 +102,8 @@ public Record set(String name, Object value) { /** * 移除一个字段 - * - * @param name - * 字段名 + * + * @param name 字段名 * @return 移除的字段值 */ public Object remove(String name) { @@ -120,7 +113,7 @@ public Object remove(String name) { /** * 返回记录中已有的字段的数量 - * + * * @return 记录中已有的字段的数量 */ public int getColumnCount() { @@ -129,7 +122,7 @@ public int getColumnCount() { /** * 返回记录中所有的字段名 - * + * * @return 记录中所有的字段名 */ public Set getColumnNames() { @@ -140,53 +133,52 @@ public Set getColumnNames() { * 返回指定字段的 int 值 *

* 如果该字段在记录中不存在,返回 -1;如果该字段的值不是 int 类型,返回 -1 - * - * @param name - * 字段名 + * + * @param name 字段名 * @return 指定字段名的 int 值。如果该字段在记录中不存在,返回 -1;如果该字段的值不是 int 类型,返回 -1 */ public int getInt(String name) { return getInt(name, -1); } - + public int getInt(String name, int dft) { try { Object val = get(name); if (null == val) return dft; return Castors.me().castTo(val, int.class); + } catch (Exception e) { } - catch (Exception e) {} return dft; } - + public long getLong(String name) { return getLong(name, -1); } - + public long getLong(String name, long dft) { try { Object val = get(name); if (null == val) return dft; return Castors.me().castTo(val, long.class); + } catch (Exception e) { } - catch (Exception e) {} return dft; } - + public double getDouble(String name) { return getDouble(name, -1); } - + public double getDouble(String name, double dft) { try { Object val = get(name); if (null == val) return dft; return Castors.me().castTo(val, double.class); + } catch (Exception e) { } - catch (Exception e) {} return dft; } @@ -194,9 +186,8 @@ public double getDouble(String name, double dft) { * 返回指定字段的 String 值 *

* 如果该字段在记录中不存在,返回 null - * - * @param name - * 字段名 + * + * @param name 字段名 * @return 指定字段的 String 值。如果该字段在记录中不存在,返回 null */ public String getString(String name) { @@ -206,13 +197,27 @@ public String getString(String name) { return Castors.me().castToString(val); } + /** + * 返回指定字段的 Blob 值 + *

+ * 如果该字段在记录中不存在,返回 null + * + * @param name 字段名 + * @return 指定字段的 Blob 值。如果该字段在记录中不存在,返回 null + */ + public Blob getBlob(String name) { + Object val = get(name); + if (null == val) + return null; + return Castors.me().castTo(val, Blob.class); + } + /** * 返回指定字段的 Timestamp 值 *

* 如果该字段在记录中不存在,返回 null - * - * @param name - * 字段名 + * + * @param name 字段名 * @return 指定字段的 Timestamp 值。如果该字段在记录中不存在,返回 null */ public Timestamp getTimestamp(String name) { @@ -224,9 +229,8 @@ public Timestamp getTimestamp(String name) { /** * 返回该记录的 JSON 字符串,并且可以设定 JSON 字符串的格式化方式 - * - * @param format - * JSON 字符串格式化方式 ,若 format 为 null ,则以 JsonFormat.nice() 格式输出 + * + * @param format JSON 字符串格式化方式 ,若 format 为 null ,则以 JsonFormat.nice() 格式输出 * @return JSON 字符串 */ public String toJson(JsonFormat format) { @@ -235,7 +239,7 @@ public String toJson(JsonFormat format) { /** * 返回该记录 JSON 格式的字符串表示 - * + * * @return 该记录 JSON 格式的字符串表示 */ public String toString() { @@ -244,19 +248,18 @@ public String toString() { /** * 根据指定的类的类型,把该记录转换成该类型的对象 - * - * @param type - * 指定的类的类型 + * + * @param type 指定的类的类型 * @return 指定的类型的对象 */ public T toPojo(Class type) { - return Lang.map2Object(map, type); + return Lang.map2Object(this, type); } public T toEntity(Entity en) { return en.getObject(this); } - + public T toEntity(Entity en, String prefix) { return en.getObject(this, prefix); } @@ -271,9 +274,8 @@ public void clear() { /** * 如果该字段在记录中存在,则返回 true - * - * @param key - * 字段名 + * + * @param key 字段名 * @return true 该字段在记录中存在 */ public boolean containsKey(Object key) { @@ -282,9 +284,8 @@ public boolean containsKey(Object key) { /** * 如果该字段值在记录中存在,则返回 true - * - * @param value - * 字段值 + * + * @param value 字段值 * @return true 该字段值在记录中存在 */ public boolean containsValue(Object value) { @@ -303,9 +304,8 @@ public boolean equals(Object out) { * 返回指定字段的值 *

* 如果该字段在记录中不存在,返回 null - * - * @param name - * 字段名 + * + * @param name 字段名 * @return 指定字段的值。如果该字段在记录中不存在,返回 null */ public Object get(Object name) { @@ -323,7 +323,7 @@ public int hashCode() { /** * 如果记录中不存在字段与值的对应关系,则返回 true - * + * * @return true 记录中不存在字段与值的对应关系 */ public boolean isEmpty() { @@ -332,7 +332,7 @@ public boolean isEmpty() { /** * 返回记录中所有的字段名 - * + * * @return 记录中所有的字段名 */ public Set keySet() { @@ -341,11 +341,9 @@ public Set keySet() { /** * 将字段与其对应的值放入该记录中 - * - * @param name - * 字段名 - * @param value - * 字段值 + * + * @param name 字段名 + * @param value 字段值 * @return 该字段之前所对应的值;如果之前该字段在该记录中不存在,则返回 null */ public Object put(String name, Object value) { @@ -360,9 +358,8 @@ public void putAll(Map out) { /** * 将字段从记录中删除 - * - * @param key - * 字段名 + * + * @param key 字段名 * @return 该字段所对应的值;如果该字段在该记录中不存在,则返回 null */ public Object remove(Object key) { @@ -371,7 +368,7 @@ public Object remove(Object key) { /** * 返回记录的记录数 - * + * * @return 记录的记录数 */ public int size() { @@ -380,7 +377,7 @@ public int size() { /** * 返回记录中所有的字段的值 - * + * * @return 记录中所有的字段的值 */ public Collection values() { @@ -389,19 +386,19 @@ public Collection values() { /** * 返回该记录对应的 Chain 对象 - * + * * @return 该记录对应的 Chain 对象 */ public Chain toChain() { return Chain.from(map); } - + public Record clone() { Record re = create(); re.putAll(this); return re; } - + public Map sensitive() { NutMap map = new NutMap(); for (String key : keys) { @@ -417,17 +414,16 @@ public int compareTo(Record re) { return 0; return re.size() > this.size() ? -1 : 1; } - + public static void setFactory(Callable factory) { Record.factory = factory; } - + public static Record create() { if (factory != null) try { return factory.call(); - } - catch (Exception e) { + } catch (Exception e) { throw Lang.wrapThrow(e); } return new Record(); diff --git a/src/org/nutz/dao/entity/annotation/ColType.java b/src/org/nutz/dao/entity/annotation/ColType.java index 70e2b1385c..ed45d30f16 100644 --- a/src/org/nutz/dao/entity/annotation/ColType.java +++ b/src/org/nutz/dao/entity/annotation/ColType.java @@ -62,6 +62,11 @@ public enum ColType { */ FLOAT, + /** + * 浮点:双精度 + */ + DOUBLE, + /** * JSON:PostgreSQL 的 JSON 类型 */ diff --git a/src/org/nutz/dao/entity/annotation/Index.java b/src/org/nutz/dao/entity/annotation/Index.java index 827bc072af..6b027e7358 100644 --- a/src/org/nutz/dao/entity/annotation/Index.java +++ b/src/org/nutz/dao/entity/annotation/Index.java @@ -1,6 +1,10 @@ package org.nutz.dao.entity.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; /** * 声明一个数据表的索引 @@ -9,6 +13,8 @@ * * @author zozoh(zozohtnt@gmail.com) */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD}) @Documented public @interface Index { @@ -23,7 +29,7 @@ String name() default ""; /** - * 按顺序给出索引的字段名(推荐,用 Java 的字段名) + * 按顺序给出索引的字段名(推荐,用 Java 的字段名). 当@Index标注在属性上, fields无效 */ String[] fields(); diff --git a/src/org/nutz/dao/entity/annotation/Table.java b/src/org/nutz/dao/entity/annotation/Table.java index 1245f4da46..ade40ad7d9 100644 --- a/src/org/nutz/dao/entity/annotation/Table.java +++ b/src/org/nutz/dao/entity/annotation/Table.java @@ -6,6 +6,9 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.nutz.dao.interceptor.PojoInterceptor; +import org.nutz.dao.interceptor.impl.DefaultPojoInterceptor; + /** * 声明的一个 POJO 所对应的数据表名。 * @@ -69,4 +72,9 @@ /** 表名后缀 */ String suffix() default ""; + + /** + * Pojo行为拦截器 + */ + Class interceptor() default DefaultPojoInterceptor.class; } diff --git a/src/org/nutz/dao/impl/DaoSupport.java b/src/org/nutz/dao/impl/DaoSupport.java index 6474262719..8543d5bc61 100644 --- a/src/org/nutz/dao/impl/DaoSupport.java +++ b/src/org/nutz/dao/impl/DaoSupport.java @@ -29,9 +29,11 @@ import org.nutz.dao.sql.Sql; import org.nutz.dao.sql.SqlContext; import org.nutz.dao.util.Daos; +import org.nutz.lang.Configurable; import org.nutz.lang.Lang; import org.nutz.lang.Mirror; import org.nutz.lang.Strings; +import org.nutz.lang.util.NutMap; import org.nutz.log.Log; import org.nutz.log.Logs; @@ -40,7 +42,7 @@ * * @author zozoh(zozohtnt@gmail.com) */ -public class DaoSupport { +public class DaoSupport implements Configurable { private static final Log log = Logs.get(); @@ -291,6 +293,14 @@ public void addInterceptor(Object it) { } public DaoInterceptor makeInterceptor(Object it) { + DaoInterceptor re = _makeInterceptor(it); + if (re != null && re instanceof DaoExecutor) { + ((NutDaoExecutor)re).setMeta(meta); + } + return re; + } + + protected DaoInterceptor _makeInterceptor(Object it) { if (it == null) return null; if (it instanceof String) { @@ -323,4 +333,13 @@ else if (it instanceof DaoInterceptor) { public DataSource getDataSource() { return dataSource; } + + public void setupProperties(NutMap conf) { + if (expert instanceof Configurable) + ((Configurable)expert).setupProperties(conf); + if (executor instanceof Configurable) + ((Configurable)executor).setupProperties(conf); + if (runner instanceof Configurable) + ((Configurable)runner).setupProperties(conf); + } } diff --git a/src/org/nutz/dao/impl/EntityOperator.java b/src/org/nutz/dao/impl/EntityOperator.java index 8c33bf4386..4d1d294e1e 100644 --- a/src/org/nutz/dao/impl/EntityOperator.java +++ b/src/org/nutz/dao/impl/EntityOperator.java @@ -1,6 +1,7 @@ package org.nutz.dao.impl; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Map; @@ -9,9 +10,11 @@ import org.nutz.dao.FieldMatcher; import org.nutz.dao.entity.Entity; import org.nutz.dao.entity.MappingField; +import org.nutz.dao.entity.PkType; import org.nutz.dao.impl.sql.pojo.AbstractPItem; import org.nutz.dao.impl.sql.pojo.ConditionPItem; import org.nutz.dao.impl.sql.pojo.InsertByChainPItem; +import org.nutz.dao.interceptor.PojoInterceptor; import org.nutz.dao.sql.Criteria; import org.nutz.dao.sql.DaoStatement; import org.nutz.dao.sql.Pojo; @@ -26,16 +29,25 @@ public class EntityOperator { - Entity entity; + protected Entity entity; - NutDao dao; + protected NutDao dao; - Object myObj; + protected Object myObj; - List pojoList = new ArrayList(); + protected List pojoList = new ArrayList(); private int updateCount; + public void setMyObj(Object obj) { + if (obj.getClass().isArray()) { + this.myObj = Lang.array2list((Object[]) obj); + } else { + this.myObj = obj; + } + + } + /** * 批量执行准备好的 Dao 语句 * @@ -75,42 +87,55 @@ public Pojo addUpdate(final Entity en, final Object obj) { if (null == en) return null; + // 触发Pojo拦截器 + _fireEvent("prevUpdate", obj, en); + Pojo pojo = dao.pojoMaker.makeUpdate(en, null) - .append(Pojos.Items.cndAuto(en, Lang.first(obj))) - .setOperatingObject(obj); + .append(Pojos.Items.cndAuto(en, Lang.first(obj))) + .setOperatingObject(obj); pojoList.add(pojo); return pojo; } - + public Pojo addUpdateByPkAndCnd(Condition cnd) { return addUpdateByPkAndCnd(entity, myObj, cnd); } - + public Pojo addUpdateByPkAndCnd(final Entity en, final Object obj, final Condition cnd) { if (null == en) return null; - Pojo pojo = dao.pojoMaker.makeUpdate(en, null) - .append(Pojos.Items.cndAuto(en, Lang.first(obj))) - .setOperatingObject(obj); - pojo.append(new Static(" AND ")); + // 触发Pojo拦截器 + _fireEvent("prevUpdate", obj, en); + + Pojo pojo = dao.pojoMaker.makeUpdate(en, null); + + boolean pureCnd = en.getPkType() == PkType.UNKNOWN; + if (!pureCnd) { + pojo.append(Pojos.Items.cndAuto(en, Lang.first(obj))); + pojo.append(new Static(" AND ")); + } if (cnd instanceof Criteria) { // 只取它的where条件 - pojo.append(((Criteria)cnd).where().setTop(false)); + pojo.append(((Criteria) cnd).where().setTop(pureCnd)); } else { - pojo.append(new ConditionPItem(cnd).setTop(false)); + pojo.append(new ConditionPItem(cnd).setTop(pureCnd)); } + pojo.setOperatingObject(obj); pojoList.add(pojo); return pojo; } - public List addUpdateForIgnoreNull( final Entity en, - final Object obj, - final FieldMatcher fm) { + public List addUpdateForIgnoreNull(final Entity en, + final Object obj, + final FieldMatcher fm) { if (null == en) return null; + // 触发Pojo拦截器 + _fireEvent("prevUpdate", obj, en); + final FieldMatcher newFM; if (null == fm) newFM = FieldMatcher.make(null, null, true); @@ -122,8 +147,8 @@ public List addUpdateForIgnoreNull( final Entity en, Lang.each(obj, new Each() { public void invoke(int i, Object ele, int length) throws ExitLoop, LoopException { Pojo pojo = dao.pojoMaker.makeUpdate(en, ele) - .append(Pojos.Items.cndAuto(en, ele)) - .setOperatingObject(ele); + .append(Pojos.Items.cndAuto(en, ele)) + .setOperatingObject(ele); pojo.getContext().setFieldMatcher(newFM); re.add(pojo); } @@ -132,16 +157,25 @@ public void invoke(int i, Object ele, int length) throws ExitLoop, LoopException return re; } - + public Pojo addUpdateAndIncrIfMatch(final Entity en, final Object obj, String fieldName) { if (null == en) return null; + + // 触发Pojo拦截器 + _fireEvent("prevUpdate", obj, en); + MappingField mf = en.getField(fieldName); Pojo pojo = dao.pojoMaker.makeUpdate(en, null) - .append(new Static("," + mf.getColumnNameInSql() + "=" + mf.getColumnNameInSql() + "+1")) - .append(Pojos.Items.cndAuto(en, Lang.first(obj))) - .setOperatingObject(obj); - pojo.append(new Static("AND")).append(((AbstractPItem)Pojos.Items.cndColumn(mf, null)).setTop(false)); + .append(new Static("," + + mf.getColumnNameInSql() + + "=" + + mf.getColumnNameInSql() + + "+1")) + .append(Pojos.Items.cndAuto(en, Lang.first(obj))) + .setOperatingObject(obj); + pojo.append(new Static("AND")) + .append(((AbstractPItem) Pojos.Items.cndColumn(mf, null)).setTop(false)); pojoList.add(pojo); return pojo; } @@ -160,7 +194,7 @@ public Pojo addDeleteSelfOnly(long id) { return null; Pojo pojo = dao.pojoMaker.makeDelete(entity); - pojo.append(Pojos.Items.cndAuto(entity, myObj)); + pojo.append(Pojos.Items.cndId(entity, id)); pojo.addParamsBy(myObj); pojoList.add(pojo); return pojo; @@ -180,7 +214,8 @@ public Pojo addDeleteSelfOnly(String name) { public Pojo addDeleteSelfOnly() { if (null == entity) return null; - + // 触发Pojo拦截器 + _fireEvent("prevDelete", myObj, this.entity); Pojo pojo = dao.pojoMaker.makeDelete(entity); pojo.append(Pojos.Items.cndAuto(entity, myObj)); pojo.addParamsBy(myObj); @@ -196,6 +231,9 @@ public List addInsert(Entity en, Object obj) { if (null == en) return null; + // 触发Pojo拦截器 + _fireEvent("prevInsert", obj, en); + int len = Map.class.isAssignableFrom(obj.getClass()) ? 1 : Lang.eleSize(obj); List re = new ArrayList(len); if (len > 0) { @@ -220,15 +258,16 @@ public Pojo addInsertSelfOnly() { public Pojo addInsertSelfOnly(Entity en, Object obj) { if (null == en) return null; - Pojo pojo; if (obj instanceof Chain) { pojo = dao.pojoMaker.makePojo(SqlType.INSERT); pojo.append(Pojos.Items.entityTableName()); - pojo.append(new InsertByChainPItem((Chain)obj)); + pojo.append(new InsertByChainPItem((Chain) obj)); pojo.setEntity(en); } else { + // 触发Pojo拦截器 + _fireEvent("prevInsert", obj, en); pojo = dao.pojoMaker.makeInsert(en).setOperatingObject(obj); } pojoList.add(pojo); @@ -261,6 +300,20 @@ public Entity makeEntity(String tableName, Map map) { } public int getPojoListSize() { - return pojoList.size(); + return pojoList.size(); + } + + protected void _fireEvent(final String event, Object obj, final Entity entity) { + final PojoInterceptor pint = entity.getInterceptor(); + if (pint != null && pint.isAvailable()) { + if (obj.getClass().isArray() || obj instanceof Collection) { + Lang.each(obj, new Each() { + public void invoke(int index, Object ele, int length) { + pint.onEvent(ele, entity, event); + } + }); + } else + pint.onEvent(obj, entity, event); + } } } diff --git a/src/org/nutz/dao/impl/FileSqlManager.java b/src/org/nutz/dao/impl/FileSqlManager.java index af538e6bda..b18483ff13 100644 --- a/src/org/nutz/dao/impl/FileSqlManager.java +++ b/src/org/nutz/dao/impl/FileSqlManager.java @@ -16,6 +16,7 @@ import org.nutz.dao.Sqls; import org.nutz.dao.sql.Sql; import org.nutz.lang.Streams; +import org.nutz.lang.Strings; import org.nutz.log.Log; import org.nutz.log.Logs; import org.nutz.resource.NutResource; @@ -23,26 +24,33 @@ /** * 基于行解析的SqlManager - * @author wendal(wendal1985@gmail.com) * + * @author wendal(wendal1985@gmail.com) + * @author wizzer(wizzer.cn@gmail.com) */ public class FileSqlManager implements SqlManager { - + private static final Log log = Logs.get(); - + protected Map sqls = Collections.synchronizedMap(new LinkedHashMap()); protected String[] paths; - + protected boolean allowDuplicate = true; - + protected String pairBegin = "/*"; protected String pairEnd = "*/"; - + protected String regex = ".(sql|sqlx|sqls)$"; - + protected boolean inited; - + + protected boolean byRow; + + public void setByRow(boolean byRow) { + this.byRow = byRow; + } + public FileSqlManager() { paths = new String[]{}; } @@ -51,28 +59,54 @@ public FileSqlManager(String... paths) { this.paths = paths; } + @Override public void refresh() { for (String path : paths) { List list = Scans.me().scan(path, regex); for (NutResource res : list) { - int c = _count(); + int c = sqls.size(); log.debugf("load >> %s from root=%s", res.getName(), path); try { - add(res.getReader()); + if (byRow) { + addByRow(res.getName(), res.getReader()); + } else { + add(res.getReader()); + } } catch (IOException e) { log.warnf("fail to load %s from root=%s", res.getName(), path, e); } - log.debugf("load %d sql >> %s from root=%s", (_count() - c), res.getName(), path); + log.debugf("load %d sql >> %s from root=%s", (sqls.size() - c), res.getName(), path); + } + } + } + + public void addByRow(String fileNmae, Reader r) throws IOException { + try { + BufferedReader br = null; + if (r instanceof BufferedReader) + br = (BufferedReader) r; + else + br = new BufferedReader(r); + int i = 0; + while (br.ready()) { + i++; + String line = Streams.nextLineTrim(br); + if (Strings.isBlank(line)) + break; + addSql(fileNmae + "." + i, line); } } + finally { + Streams.safeClose(r); + } } - + public void add(Reader r) throws IOException { try { BufferedReader br = null; if (r instanceof BufferedReader) - br = (BufferedReader)r; + br = (BufferedReader) r; else br = new BufferedReader(r); StringBuilder key = new StringBuilder(); @@ -91,7 +125,7 @@ public void add(Reader r) throws IOException { } key.setLength(0); sb.setLength(0); - + if (line.endsWith(pairEnd)) { if (line.length() > 4) key.append(line.substring(2, line.length() - 2).trim()); @@ -120,7 +154,7 @@ public void add(Reader r) throws IOException { sb.append("\n"); sb.append(line); } - + // 最后一个sql也许是存在的 if (key.length() > 0 && sb.length() > 0) { addSql(key.toString(), sb.toString()); @@ -130,7 +164,8 @@ public void add(Reader r) throws IOException { Streams.safeClose(r); } } - + + @Override public String get(String key) throws SqlNotFoundException { _check_inited(); String sql = sqls.get(key); @@ -139,11 +174,13 @@ public String get(String key) throws SqlNotFoundException { return sql; } + @Override public Sql create(String key) throws SqlNotFoundException { _check_inited(); return Sqls.create(get(key)); } + @Override public List createCombo(String... keys) { if (keys.length == 0) keys = keys(); @@ -154,45 +191,45 @@ public List createCombo(String... keys) { return list; } + @Override public int count() { _check_inited(); return sqls.size(); } - - public int _count() { - return sqls.size(); - } + @Override public String[] keys() { _check_inited(); Set keys = sqls.keySet(); return keys.toArray(new String[keys.size()]); } + @Override public synchronized void addSql(String key, String value) { log.debugf("key=[%s], sql=[%s]", key, value); if (!isAllowDuplicate() && sqls.containsKey(key)) - throw new DaoException("Duplicate sql key=[" +key + "]"); + throw new DaoException("Duplicate sql key=[" + key + "]"); sqls.put(key, value); } + @Override public void remove(String key) { _check_inited(); sqls.remove(key); } - + public void setAllowDuplicate(boolean allowDuplicate) { this.allowDuplicate = allowDuplicate; } - + public boolean isAllowDuplicate() { return allowDuplicate; } - + public void setPaths(String[] paths) { this.paths = paths; } - + public String getRegex() { return regex; } @@ -201,27 +238,27 @@ public FileSqlManager setRegex(String regex) { this.regex = regex; return this; } - + public void setPairBegin(String pairBegin) { this.pairBegin = pairBegin; } - + public void setPairEnd(String pairEnd) { this.pairEnd = pairEnd; } - + public String getPairBegin() { return pairBegin; } - + public String getPairEnd() { return pairEnd; } - + public String[] getPaths() { return paths; } - + protected void _check_inited() { if (!inited) { synchronized (this) { @@ -232,4 +269,9 @@ protected void _check_inited() { } } } + + @Override + public void clear() { + sqls.clear(); + } } diff --git a/src/org/nutz/dao/impl/NutDao.java b/src/org/nutz/dao/impl/NutDao.java index 54fe052756..0351eb6aa1 100644 --- a/src/org/nutz/dao/impl/NutDao.java +++ b/src/org/nutz/dao/impl/NutDao.java @@ -5,7 +5,9 @@ import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import javax.sql.DataSource; @@ -24,7 +26,6 @@ import org.nutz.dao.entity.LinkField; import org.nutz.dao.entity.LinkVisitor; import org.nutz.dao.entity.MappingField; -import org.nutz.dao.entity.PkType; import org.nutz.dao.entity.Record; import org.nutz.dao.impl.link.DoClearLinkVisitor; import org.nutz.dao.impl.link.DoClearRelationByHostFieldLinkVisitor; @@ -57,12 +58,14 @@ import org.nutz.dao.sql.Sql; import org.nutz.dao.util.Daos; import org.nutz.dao.util.Pojos; +import org.nutz.dao.util.cri.SimpleCriteria; import org.nutz.dao.util.cri.SqlExpressionGroup; import org.nutz.lang.ContinueLoop; import org.nutz.lang.Each; import org.nutz.lang.ExitLoop; import org.nutz.lang.Lang; import org.nutz.lang.LoopException; +import org.nutz.lang.Mirror; import org.nutz.lang.Strings; import org.nutz.trans.Atom; import org.nutz.trans.Molecule; @@ -132,21 +135,45 @@ public T insert(final T obj) { final EntityOperator opt = _optBy(first); if (null == opt) return null; + int size = Lang.eleSize(obj); - opt.addInsert(opt.entity, first); if (size > 1) { - if (opt.getPojoListSize() == 1) { - // 单一操作,可以转为批量插入 - return fastInsert(obj); - } - Lang.each(obj, false, new Each() { - public void invoke(int i, Object ele, int length) throws ExitLoop, LoopException { - if (i != 0) - opt.addInsert(opt.entity, ele); - } - }); + if (!opt.entity.hasInsertMacroes()) { + // 单一操作,可以转为批量插入 + return fastInsert(obj); + } } + Lang.each(obj, false, new Each() { + public void invoke(int i, Object ele, int length) throws ExitLoop, LoopException { + opt.addInsert(opt.entity, ele); + } + }); + opt.exec(); + + return obj; + } + + public T insert(Entity entity, final T obj) { + final EntityOperator opt = _opt(entity); + opt.setMyObj(obj); + if (null == opt.myObj) + return null; + + int size = Lang.eleSize(obj); + if (size > 1) { + if (!opt.entity.hasInsertMacroes()) { + // 单一操作,可以转为批量插入 + fastInsert(entity, obj); + return obj; + } + } + Lang.each(obj, false, new Each() { + public void invoke(int i, Object ele, int length) throws ExitLoop, LoopException { + opt.addInsert(opt.entity, ele); + } + }); opt.exec(); + return obj; } @@ -185,8 +212,24 @@ public void insert(Class classOfT, Chain chain) { opt.exec(); } + public void insert(Entity entity, Chain chain) { + if (chain.isSpecial()) { + Daos.insertBySpecialChain(this, entity, null, chain); + return; + } + EntityOperator opt = _opt(entity); + opt.myObj = chain; + opt.addInsertSelfOnly(); + // insert(chain.toObject(classOfT));// TODO 这样的效率,未免太低了,需要改进 + opt.exec(); + } + public T fastInsert(T obj) { - EntityOperator opt = _optBy(obj); + return fastInsert(obj, false); + } + + public T fastInsert(T obj, boolean detectAllColumns) { + EntityOperator opt = _optBy(obj, detectAllColumns); if (null == opt) return null; opt.addInsertSelfOnly(); @@ -194,6 +237,13 @@ public T fastInsert(T obj) { return obj; } + public void fastInsert(Entity entity, Object obj) { + EntityOperator opt = _opt(entity); + opt.setMyObj(obj); + opt.addInsertSelfOnly(); + opt.exec(); + } + public T insertWith(T obj, String regex) { EntityOperator opt = _optBy(obj); if (null == opt) @@ -216,7 +266,7 @@ public void visit(Object obj, LinkField lnk) { opt.entity.visitManyMany(obj, regex, doInsert(opt)); opt.entity.visitManyMany(obj, regex, doInsertRelation(opt)); opt.exec(); - + if (flag[0]) { opt = _optBy(obj); final LinkVisitor _one = doInsert(opt); @@ -276,28 +326,28 @@ public int update(final Object obj, String actived) { if (Strings.isBlank(actived)) return update(obj); - + return update(obj, FieldFilter.create(first.getClass(), actived)); } - + public int update(final Object obj, String actived, String locked, boolean ignoreNull) { Object first = Lang.first(obj); if (null == first) return 0; return update(obj, FieldFilter.create(first.getClass(), actived, locked, ignoreNull)); } - + public int update(final Object obj, FieldFilter fieldFilter) { if (fieldFilter == null) return update(obj); - + return fieldFilter.run(new Molecule() { public void run() { setObj(update(obj)); } }); } - + public int update(final Object obj, FieldFilter fieldFilter, final Condition cnd) { if (fieldFilter == null) return update(obj, cnd); @@ -307,7 +357,7 @@ public void run() { } }); } - + public int update(Object obj, Condition cnd) { if (cnd == null) return update(obj); @@ -344,12 +394,100 @@ public int update(Class classOfT, Chain chain, Condition cnd) { return opt.getUpdateCount(); } + public int update(Entity entity, Object obj) { + if (null == obj || null == entity) { + return 0; + } + EntityOperator opt = _opt(entity); + opt.setMyObj(obj); + if (null == opt.myObj) + return 0; + + opt.addUpdate(); + opt.exec(); + return opt.getUpdateCount(); + } + + public int update(Entity entity, final Object obj, String actived) { + if (null == obj || null == entity) { + return 0; + } + Object first = Lang.first(obj); + if (null == first) + return 0; + + if (Strings.isBlank(actived)) + return update(entity, obj); + + FieldFilter filter = FieldFilter.create(first.getClass(), actived); + return update(entity, obj, filter); + } + + public int update(Entity entity, + final Object obj, + String actived, + String locked, + boolean ignoreNull) { + if (null == obj || null == entity) { + return 0; + } + Object first = Lang.first(obj); + if (null == first) + return 0; + + FieldFilter filter = FieldFilter.create(first.getClass(), actived, locked, ignoreNull); + return update(entity, obj, filter); + } + + public int update(final Entity entity, final Object obj, FieldFilter fieldFilter) { + if (fieldFilter == null) + return update(entity, obj); + + return fieldFilter.run(new Molecule() { + public void run() { + setObj(update(entity, obj)); + } + }); + } + + public int update(final Entity entity, + final Object obj, + FieldFilter fieldFilter, + final Condition cnd) { + if (fieldFilter == null) + return update(entity, obj, cnd); + return fieldFilter.run(new Molecule() { + public void run() { + setObj(update(entity, obj, cnd)); + } + }); + } + + public int update(Entity entity, Object obj, Condition cnd) { + if (cnd == null) + return update(obj); + EntityOperator opt = _opt(entity); + opt.setMyObj(obj); + if (null == opt.myObj) + return 0; + opt.addUpdateByPkAndCnd(cnd); + opt.exec(); + return opt.getUpdateCount(); + } + + public int update(Entity entity, Chain chain, Condition cnd) { + EntityOperator opt = _opt(entity); + opt.addUpdate(chain, cnd); + opt.exec(); + return opt.getUpdateCount(); + } + public T updateWith(T obj, final String regex) { if (null == obj) return null; Lang.each(obj, false, new Each() { - public void invoke(int index, Object ele, int length) throws ExitLoop, ContinueLoop, - LoopException { + public void invoke(int index, Object ele, int length) + throws ExitLoop, ContinueLoop, LoopException { EntityOperator opt = _optBy(ele); if (null == opt) return; @@ -369,8 +507,8 @@ public T updateLinks(T obj, final String regex) { if (null == obj) return null; Lang.each(obj, false, new Each() { - public void invoke(int index, Object ele, int length) throws ExitLoop, ContinueLoop, - LoopException { + public void invoke(int index, Object ele, int length) + throws ExitLoop, ContinueLoop, LoopException { EntityOperator opt = _optBy(ele); if (null == opt) return; @@ -399,7 +537,11 @@ public int updateRelation(Class classOfT, String regex, Chain chain, Conditio public int delete(Class classOfT, long id) { Entity en = holder.getEntity(classOfT); - Pojo pojo = pojoMaker.makeDelete(en).append(Pojos.Items.cndId(en, id)); + return delete(en, id); + } + + public int delete(Entity entity, long id) { + Pojo pojo = pojoMaker.makeDelete(entity).append(Pojos.Items.cndId(entity, id)); pojo.addParamsBy(id); _exec(pojo); return pojo.getUpdateCount(); @@ -407,8 +549,12 @@ public int delete(Class classOfT, long id) { public int delete(Class classOfT, String name) { Entity en = holder.getEntity(classOfT); - Pojo pojo = pojoMaker.makeDelete(en) - .append(Pojos.Items.cndName(en, name)) + return delete(en, name); + } + + public int delete(Entity entity, String name) { + Pojo pojo = pojoMaker.makeDelete(entity) + .append(Pojos.Items.cndName(entity, name)) .addParamsBy(name); _exec(pojo); return pojo.getUpdateCount(); @@ -435,8 +581,8 @@ public int deleteWith(Object obj, final String regex) { return 0; final int[] re = new int[1]; Lang.each(obj, false, new Each() { - public void invoke(int index, Object ele, int length) throws ExitLoop, ContinueLoop, - LoopException { + public void invoke(int index, Object ele, int length) + throws ExitLoop, ContinueLoop, LoopException { EntityOperator opt = _optBy(ele); if (null == opt) return; @@ -457,8 +603,8 @@ public int deleteLinks(Object obj, final String regex) { return 0; final int[] re = new int[1]; Lang.each(obj, false, new Each() { - public void invoke(int index, Object ele, int length) throws ExitLoop, ContinueLoop, - LoopException { + public void invoke(int index, Object ele, int length) + throws ExitLoop, ContinueLoop, LoopException { EntityOperator opt = _optBy(ele); if (null == opt) return; @@ -474,14 +620,19 @@ public void invoke(int index, Object ele, int length) throws ExitLoop, ContinueL } public List query(Class classOfT, Condition cnd, Pager pager) { - Pojo pojo = pojoMaker.makeQuery(holder.getEntity(classOfT)) + Entity entity = holder.getEntity(classOfT); + return query(entity, cnd, pager); + } + + public List query(Entity entity, Condition cnd, Pager pager) { + Pojo pojo = pojoMaker.makeQuery(entity) .append(Pojos.Items.cnd(cnd)) .addParamsBy("*") .setPager(pager) .setAfter(_pojo_queryEntity); expert.formatQuery(pojo); _exec(pojo); - return pojo.getList(classOfT); + return pojo.getList(entity.getType()); } public List query(Class classOfT, Condition cnd) { @@ -489,7 +640,12 @@ public List query(Class classOfT, Condition cnd) { } public int each(Class classOfT, Condition cnd, Pager pager, Each callback) { - Pojo pojo = pojoMaker.makeQuery(holder.getEntity(classOfT)) + Entity entity = holder.getEntity(classOfT); + return each(entity, cnd, pager, callback); + } + + public int each(Entity entity, Condition cnd, Pager pager, Each callback) { + Pojo pojo = pojoMaker.makeQuery(entity) .append(Pojos.Items.cnd(cnd)) .addParamsBy("*") .setPager(pager) @@ -501,15 +657,15 @@ public int each(Class classOfT, Condition cnd, Pager pager, Each callb _exec(pojo); return pojo.getInt(); } - + public int each(Class classOfT, Condition cnd, Each callback) { return each(classOfT, cnd, Pojos.Items.pager(cnd), callback); } - + public List query(String tableName, Condition cnd, Pager pager) { return query(tableName, cnd, pager, "*"); } - + public List query(String tableName, Condition cnd, Pager pager, String fields) { Pojo pojo = pojoMaker.makeQuery(tableName, fields) .addParamsBy(fields) @@ -525,7 +681,11 @@ public List query(String tableName, Condition cnd) { return query(tableName, cnd, Pojos.Items.pager(cnd)); } - public int each(String tableName, Condition cnd, Pager pager, Each callback, String fields) { + public int each(String tableName, + Condition cnd, + Pager pager, + Each callback, + String fields) { Pojo pojo = pojoMaker.makeQuery(tableName, fields) .addParamsBy(fields) .setPager(pager) @@ -537,7 +697,7 @@ public int each(String tableName, Condition cnd, Pager pager, Each callb _exec(pojo); return pojo.getInt(); } - + public int each(String tableName, Condition cnd, Pager pager, Each callback) { return each(tableName, cnd, pager, callback, "*"); } @@ -548,54 +708,73 @@ public int each(String tableName, Condition cnd, Each callback) { public T fetch(Class classOfT, long id) { Entity en = holder.getEntity(classOfT); - if (en.getIdField() == null) - throw new DaoException("Need @Id for " + classOfT); - Pojo pojo = pojoMaker.makeQuery(en) - .append(Pojos.Items.cndId(en, id)) + return fetch(en, id); + } + + public T fetch(Entity entity, long id) { + if (entity.getIdField() == null) + throw new DaoException("Need @Id for " + entity.getType()); + Pojo pojo = pojoMaker.makeQuery(entity) + .append(Pojos.Items.cndId(entity, id)) .addParamsBy(id) .setAfter(_pojo_fetchEntity); _exec(pojo); - return pojo.getObject(classOfT); + return pojo.getObject(entity.getType()); } public T fetch(Class classOfT, String name) { if (name == null) throw new IllegalArgumentException("name MUST NOT NULL!"); Entity en = holder.getEntity(classOfT); - if (en.getNameField() == null) - throw new DaoException("Need @Name for " + classOfT); - Pojo pojo = pojoMaker.makeQuery(en) - .append(Pojos.Items.cndName(en, name)) + return fetch(en, name); + } + + public T fetch(Entity entity, String name) { + if (name == null) + throw new IllegalArgumentException("name MUST NOT NULL!"); + if (entity.getNameField() == null) + throw new DaoException("Need @Name for " + entity.getType()); + Pojo pojo = pojoMaker.makeQuery(entity) + .append(Pojos.Items.cndName(entity, name)) .addParamsBy(name) .setAfter(_pojo_fetchEntity); _exec(pojo); - return pojo.getObject(classOfT); + return pojo.getObject(entity.getType()); } public T fetchx(Class classOfT, Object... pks) { Entity en = holder.getEntity(classOfT); - Pojo pojo = pojoMaker.makeQuery(en) - .append(Pojos.Items.cndPk(en, pks)) + return fetchx(en, pks); + } + + public T fetchx(Entity entity, Object... pks) { + Pojo pojo = pojoMaker.makeQuery(entity) + .append(Pojos.Items.cndPk(entity, pks)) .setAfter(_pojo_fetchEntity); _exec(pojo); - return pojo.getObject(classOfT); + return pojo.getObject(entity.getType()); } public T fetch(Class classOfT, Condition cnd) { - Pojo pojo = pojoMaker.makeQuery(holder.getEntity(classOfT)) + Entity entity = holder.getEntity(classOfT); + return fetch(entity, cnd); + } + + public T fetch(Entity entity, Condition cnd) { + Pojo pojo = pojoMaker.makeQuery(entity) .append(Pojos.Items.cnd(cnd)) .addParamsBy("*") .setPager(createPager(1, 1)) .setAfter(_pojo_fetchEntity); expert.formatQuery(pojo); _exec(pojo); - return pojo.getObject(classOfT); + return pojo.getObject(entity.getType()); } public Record fetch(String tableName, Condition cnd) { return fetch(tableName, cnd, "*"); } - + public Record fetch(String tableName, Condition cnd, String fields) { Pojo pojo = pojoMaker.makeQuery(tableName, fields) .append(Pojos.Items.cnd(cnd)) @@ -641,7 +820,12 @@ public void invoke(int index, Object ele, int length) { } public int clear(Class classOfT, Condition cnd) { - Pojo pojo = pojoMaker.makeDelete(holder.getEntity(classOfT)).append(Pojos.Items.cnd(cnd)); + Entity entity = holder.getEntity(classOfT); + return clear(entity, cnd); + } + + public int clear(Entity entity, Condition cnd) { + Pojo pojo = pojoMaker.makeDelete(entity).append(Pojos.Items.cnd(cnd)); _exec(pojo); return pojo.getUpdateCount(); } @@ -687,11 +871,19 @@ public int count(Class classOfT, Condition cnd) { return _count(en, en.getViewName(), cnd); } + public int count(Entity en, Condition cnd) { + return _count(en, en.getViewName(), cnd); + } + public int count(Class classOfT) { Entity en = holder.getEntity(classOfT); return _count(en, en.getViewName(), null); } + public int count(Entity en) { + return _count(en, en.getViewName(), null); + } + public int count(String tableName) { return count(tableName, null); } @@ -707,6 +899,11 @@ private int _count(Entity en, String tableName, Condition cnd) { pojo.setEntity(en); // 高级条件接口,直接得到 WHERE 子句 if (cnd instanceof Criteria) { + if (cnd instanceof SimpleCriteria) { + String beforeWhere = ((SimpleCriteria) cnd).getBeforeWhere(); + if (!Strings.isBlank(beforeWhere)) + pojo.append(Pojos.Items.wrap(beforeWhere)); + } pojo.append(((Criteria) cnd).where()); // MySQL/PgSQL/SqlServer 与 Oracle/H2的结果会不一样,奇葩啊 GroupBy gb = ((Criteria) cnd).getGroupBy(); @@ -819,6 +1016,54 @@ public void invoke(Connection conn) throws Exception { return en; } + public synchronized Entity create(final Entity en, boolean dropIfExists) { + if (exists(en.getTableName())) { + if (dropIfExists) { + expert.dropEntity(this, en); + } else { + expert.createRelation(this, en); + return en; + } + } + expert.createEntity(this, en); + // 最后在数据库中验证一下实体各个字段 + run(new ConnCallback() { + public void invoke(Connection conn) throws Exception { + expert.setupEntityField(conn, en); + } + }); + return en; + } + + public synchronized > Entity create(T map, boolean dropIfExists) { + String tableName = (String) map.get(".table"); + if (Strings.isBlank(tableName)) + throw new DaoException("need .table!!"); + return create(tableName, map, dropIfExists); + } + + public synchronized > Entity create(String tableName, + T map, + boolean dropIfExists) { + final Entity en = holder.makeEntity(tableName, map); + if (exists(en.getTableName())) { + if (dropIfExists) { + expert.dropEntity(this, en); + } else { + expert.createRelation(this, en); + return en; + } + } + expert.createEntity(this, en); + // 最后在数据库中验证一下实体各个字段 + run(new ConnCallback() { + public void invoke(Connection conn) throws Exception { + expert.setupEntityField(conn, en); + } + }); + return en; + } + public boolean drop(Class classOfT) { Entity en = holder.getEntity(classOfT); if (!exists(en.getTableName())) @@ -838,6 +1083,10 @@ public boolean exists(Class classOfT) { return exists(getEntity(classOfT).getViewName()); } + public boolean exists(Entity entity) { + return exists(entity.getViewName()); + } + public boolean exists(final String tableName) { final boolean[] ee = {false}; this.run(new ConnCallback() { @@ -909,13 +1158,18 @@ public void visit(final Object obj, final LinkField lnk) { }; } - private LinkVisitor doLinkQuery(final EntityOperator opt, final Condition cnd) { + private LinkVisitor doLinkQuery(final EntityOperator opt, + final Condition _cnd, + final Map cnds) { return new LinkVisitor() { public void visit(final Object obj, final LinkField lnk) { Pojo pojo = opt.maker().makeQuery(lnk.getLinkedEntity()); pojo.setOperatingObject(obj); PItem[] _cndItems = Pojos.Items.cnd(lnk.createCondition(obj)); pojo.append(_cndItems); + Condition cnd = _cnd; + if (_cnd == null && cnds != null) + cnd = cnds.get(lnk.getLinkedField().getName()); if (cnd != null) { if (cnd instanceof Criteria) { Criteria cri = (Criteria) cnd; @@ -963,16 +1217,39 @@ EntityOperator _opt(Class classOfT) { } EntityOperator _optBy(Object obj) { + return _optBy(obj, false); + } + + EntityOperator _optBy(Object obj, boolean detectAllColumns) { // 阻止空对象 if (null == obj) return null; + Entity en = null; + // for issue 1425 + if (detectAllColumns && Lang.eleSize(obj) > 1) { + Object first = Lang.first(obj); + if (first != null && first instanceof Map) { + final Map tmp = new HashMap(); + Lang.each(obj, new Each() { + @SuppressWarnings({"unchecked", "rawtypes"}) + public void invoke(int index, Object ele, int length) + throws ExitLoop, ContinueLoop, LoopException { + tmp.putAll((Map) ele); + } + }); + en = holder.getEntityBy(tmp); + } + } + if (en == null) { + en = holder.getEntityBy(obj); + } // 对象是否有内容,这里会考虑集合与数组 - Entity en = holder.getEntityBy(obj); + if (null == en) return null; // 创建操作对象 EntityOperator re = _opt(en); - re.myObj = obj.getClass().isArray() ? Lang.array2list((Object[]) obj) : obj; + re.setMyObj(obj); return re; } @@ -989,7 +1266,13 @@ public void setExpert(Object obj) throws Exception { this.expert = Jdbcs.getExpert(name, ""); if (this.expert == null) { if (name.contains(".")) { - this.expert = (JdbcExpert) Lang.loadClass(name).newInstance(); + Class klass = Lang.loadClass(name); + try { + this.expert = (JdbcExpert) Mirror.me(klass).born(Jdbcs.getConf()); + } + catch (Throwable e) { + this.expert = (JdbcExpert) Mirror.me(klass).born(); + } } else { throw new DaoException("not such expert=" + obj); } @@ -1002,44 +1285,50 @@ public void setExpert(Object obj) throws Exception { setDataSource(ds); } } - + public Sql execute(Sql sql) { if (sql != null) execute(new Sql[]{sql}); return sql; } - + public T insert(final T t, boolean ignoreNull, boolean ignoreZero, boolean ignoreBlankStr) { - Object obj = Lang.first(t); - Entity en = getEntity(obj.getClass()); - List names = new ArrayList(); - for (MappingField mf : en.getMappingFields()) { - if (mf.isName() || mf.isPk() || mf.isId()) { + Object obj = Lang.first(t); + Entity en = getEntity(obj.getClass()); + List names = new ArrayList(); + for (MappingField mf : en.getMappingFields()) { + if (mf.isName() || mf.isPk() || mf.isId()) { names.add(mf.getName()); - continue; - } - Object tmp = mf.getValue(obj); - if (ignoreNull && tmp == null) { - continue; - } - if (ignoreZero && (tmp == null || (tmp instanceof Number && ((Number)tmp).intValue() == 0))) { - continue; - } - if (ignoreBlankStr && (tmp instanceof CharSequence && Strings.isBlank((CharSequence)tmp))) - continue; - names.add(mf.getName()); - } - FieldFilter ff = FieldFilter.create(obj.getClass(), "^("+Strings.join("|", names.toArray())+")$"); - Molecule m = new Molecule() { - public void run() { - insert(t); - setObj(t); - } - }; - return ff.run(m); - } - - public List query(final Class classOfT, final Condition cnd, final Pager pager, FieldMatcher matcher) { + continue; + } + Object tmp = mf.getValue(obj); + if (ignoreNull && tmp == null) { + continue; + } + if (ignoreZero + && (tmp == null || (tmp instanceof Number && ((Number) tmp).intValue() == 0))) { + continue; + } + if (ignoreBlankStr + && (tmp instanceof CharSequence && Strings.isBlank((CharSequence) tmp))) + continue; + names.add(mf.getName()); + } + FieldFilter ff = FieldFilter.create(obj.getClass(), + "^(" + Strings.join("|", names.toArray()) + ")$"); + Molecule m = new Molecule() { + public void run() { + insert(t); + setObj(t); + } + }; + return ff.run(m); + } + + public List query(final Class classOfT, + final Condition cnd, + final Pager pager, + FieldMatcher matcher) { if (matcher == null) return query(classOfT, cnd, pager); FieldFilter ff = FieldFilter.create(classOfT, matcher); @@ -1050,8 +1339,11 @@ public void run() { }; return ff.run(m); } - - public List query(final Class classOfT, final Condition cnd, final Pager pager, String regex) { + + public List query(final Class classOfT, + final Condition cnd, + final Pager pager, + String regex) { if (regex == null) return query(classOfT, cnd, pager); FieldFilter ff = FieldFilter.create(classOfT, FieldMatcher.make(regex, null, false)); @@ -1062,33 +1354,49 @@ public void run() { }; return ff.run(m); } - + public T insertOrUpdate(T t) { return insertOrUpdate(t, null, null); } - - public T insertOrUpdate(T t, FieldFilter insertFieldFilter, FieldFilter updateFieldFilter) { + + public T insertOrUpdate(T t, + final FieldFilter insertFieldFilter, + final FieldFilter updateFieldFilter) { if (t == null) return null; Object obj = Lang.first(t); - Entity en = getEntity(obj.getClass()); - if (en.getPkType() == PkType.NAME) { - MappingField mf = en.getNameField(); - Object val = mf.getValue(obj); - if (val == null || fetch(obj.getClass(), Cnd.where(mf.getName(), "=", val)) == null) { - insert(t, insertFieldFilter); - } else { - update(t, updateFieldFilter); + final Entity en = getEntity(obj.getClass()); + Lang.each(t, new Each() { + + public void invoke(int index, Object ele, int length) + throws ExitLoop, ContinueLoop, LoopException { + + boolean shall_update = false; + MappingField mf = en.getNameField(); + if (mf != null) { + Object val = mf.getValue(ele); + if (val != null + && fetch(en.getType(), Cnd.where(mf.getName(), "=", val)) != null) { + shall_update = true; + } + } else if (en.getIdField() != null) { + mf = en.getIdField(); + Object val = mf.getValue(ele); + if (val != null && fetch(ele) != null) { + shall_update = true; + } + } else { + shall_update = fetch(ele) != null; + } + if (shall_update) + update(ele, updateFieldFilter); + else + insert(ele, insertFieldFilter); } - return t; - } - if (fetch(t) != null) - update(t, updateFieldFilter); - else - insert(t, insertFieldFilter); + }); return t; } - + public int updateAndIncrIfMatch(final Object obj, FieldFilter fieldFilter, String fieldName) { final EntityOperator opt = _optBy(obj); if (null == opt) @@ -1096,15 +1404,53 @@ public int updateAndIncrIfMatch(final Object obj, FieldFilter fieldFilter, Strin if (fieldName == null) fieldName = "version"; if (fieldFilter == null) - fieldFilter = FieldFilter.create(opt.entity.getType(), null, "^"+fieldName+"$", false); + fieldFilter = FieldFilter.create(opt.entity.getType(), + null, + "^" + fieldName + "$", + false); + else { + FieldMatcher fieldMatcher = fieldFilter.map().get(opt.entity.getType()); + if (fieldMatcher == null) { + fieldMatcher = FieldMatcher.make(null, "^" + fieldName + "$", false); + fieldFilter.map().put(opt.entity.getType(), fieldMatcher); + } else { + if (fieldMatcher.getLocked() == null) { + fieldMatcher.setLocked("^" + fieldName + "$"); + } + } + } + final String _fieldName = fieldName; + fieldFilter.run(new Atom() { + public void run() { + opt.addUpdateAndIncrIfMatch(opt.entity, obj, _fieldName); + opt.exec(); + } + }); + return opt.getUpdateCount(); + } + + public int updateAndIncrIfMatch(Entity en, + final Object obj, + FieldFilter fieldFilter, + String fieldName) { + final EntityOperator opt = _opt(en); + if (null == opt) + return 0; + if (fieldName == null) + fieldName = "version"; + if (fieldFilter == null) + fieldFilter = FieldFilter.create(opt.entity.getType(), + null, + "^" + fieldName + "$", + false); else { FieldMatcher fieldMatcher = fieldFilter.map().get(opt.entity.getType()); if (fieldMatcher == null) { - fieldMatcher = FieldMatcher.make(null, "^"+fieldName+"$", false); + fieldMatcher = FieldMatcher.make(null, "^" + fieldName + "$", false); fieldFilter.map().put(opt.entity.getType(), fieldMatcher); } else { if (fieldMatcher.getLocked() == null) { - fieldMatcher.setLocked("^"+fieldName+"$"); + fieldMatcher.setLocked("^" + fieldName + "$"); } } } @@ -1112,7 +1458,8 @@ public int updateAndIncrIfMatch(final Object obj, FieldFilter fieldFilter, Strin fieldFilter.run(new Atom() { public void run() { opt.addUpdateAndIncrIfMatch(opt.entity, obj, _fieldName); - opt.exec();} + opt.exec(); + } }); return opt.getUpdateCount(); } @@ -1120,83 +1467,130 @@ public void run() { public int updateWithVersion(Object obj) { return updateWithVersion(obj, null); } - + public int updateWithVersion(Object obj, FieldFilter fieldFilter) { - return updateAndIncrIfMatch(obj, fieldFilter, getEntity(Lang.first(obj).getClass()).getVersionField().getName()); + return updateAndIncrIfMatch(obj, + fieldFilter, + getEntity(Lang.first(obj).getClass()).getVersionField() + .getName()); } - + public T fetchByJoin(Class klass, String regex, long id) { Entity en = getEntity(klass); MappingField mf = en.getIdField(); return fetchByJoin(klass, regex, en, mf, id); } - + public T fetchByJoin(Class klass, String regex, String name) { Entity en = getEntity(klass); MappingField mf = en.getNameField(); return fetchByJoin(klass, regex, en, mf, name); } - - public T fetchByJoin(Class klass, String regex, Entity en, MappingField mf, Object value) { + + public T fetchByJoin(Class klass, + String regex, + Entity en, + MappingField mf, + Object value) { String key = en.getTableName() + "." + mf.getColumnNameInSql(); T t = fetchByJoin(klass, regex, Cnd.where(key, "=", value)); if (t != null) _fetchLinks(t, regex, false, true, true, null); return t; } - + public T fetchByJoin(Class classOfT, String regex, Condition cnd) { + return fetchByJoin(classOfT, regex, cnd, null); + } + + public T fetchByJoin(Class classOfT, + String regex, + Condition cnd, + Map cnds) { Pojo pojo = pojoMaker.makeQueryByJoin(holder.getEntity(classOfT), regex) - .append(Pojos.Items.cnd(cnd)) - .addParamsBy("*") - .setPager(createPager(1, 1)) - .setAfter(new PojoFetchEntityByJoinCallback(regex)); + .append(Pojos.Items.cnd(cnd)) + .addParamsBy("*") + .setPager(createPager(1, 1)) + .setAfter(new PojoFetchEntityByJoinCallback(regex)); expert.formatQuery(pojo); _exec(pojo); T t = pojo.getObject(classOfT); if (t != null) - _fetchLinks(t, regex, false, true, true, null); + _fetchLinks(t, regex, false, true, true, null, cnds); return t; } - + public List queryByJoin(Class classOfT, String regex, Condition cnd) { return this.queryByJoin(classOfT, regex, cnd, null); } - + public List queryByJoin(Class classOfT, String regex, Condition cnd, Pager pager) { - Pojo pojo = pojoMaker.makeQueryByJoin(holder.getEntity(classOfT), regex) - .append(Pojos.Items.cnd(cnd)) - .addParamsBy("*") - .setPager(pager) - .setAfter(new PojoQueryEntityByJoinCallback(regex)); - expert.formatQuery(pojo); - _exec(pojo); - List list = pojo.getList(classOfT); - if (list != null && list.size() > 0) - for (T t : list) { - _fetchLinks(t, regex, false, true, true, null); - } - return list; - } - - protected Object _fetchLinks(Object t, String regex, boolean visitOne, boolean visitMany, boolean visitManyMany, final Condition cnd) { + return queryByJoin(classOfT, regex, cnd, pager, null); + } + + public List queryByJoin(Class classOfT, + String regex, + Condition cnd, + Pager pager, + Map cnds) { + Pojo pojo = pojoMaker.makeQueryByJoin(holder.getEntity(classOfT), regex) + .append(Pojos.Items.cnd(cnd)) + .addParamsBy("*") + .setPager(pager) + .setAfter(new PojoQueryEntityByJoinCallback(regex)); + expert.formatQuery(pojo); + _exec(pojo); + List list = pojo.getList(classOfT); + if (list != null && list.size() > 0) + for (T t : list) { + _fetchLinks(t, regex, false, true, true, null, cnds); + } + return list; + } + + public int countByJoin(Class classOfT, String regex, Condition cnd) { + Pojo pojo = pojoMaker.makeCountByJoin(holder.getEntity(classOfT), regex) + .append(Pojos.Items.cnd(cnd)) + .addParamsBy("*") + .setAfter(_pojo_fetchInt); + expert.formatQuery(pojo); + _exec(pojo); + return pojo.getInt(0); + } + + protected Object _fetchLinks(Object t, + String regex, + boolean visitOne, + boolean visitMany, + boolean visitManyMany, + final Condition cnd) { + return _fetchLinks(t, regex, visitOne, visitMany, visitManyMany, cnd, null); + } + + protected Object _fetchLinks(Object t, + String regex, + boolean visitOne, + boolean visitMany, + boolean visitManyMany, + final Condition cnd, + final Map cnds) { EntityOperator opt = _optBy(t); if (null == opt) return t; if (visitMany) - opt.entity.visitMany(t, regex, doLinkQuery(opt, cnd)); + opt.entity.visitMany(t, regex, doLinkQuery(opt, cnd, cnds)); if (visitManyMany) - opt.entity.visitManyMany(t, regex, doLinkQuery(opt, cnd)); + opt.entity.visitManyMany(t, regex, doLinkQuery(opt, cnd, cnds)); if (visitOne) opt.entity.visitOne(t, regex, doFetch(opt)); opt.exec(); return t; } - + public EntityHolder getEntityHolder() { return holder; } - + public T insert(T obj, String actived) { Object first = Lang.first(obj); if (null == first) @@ -1204,16 +1598,16 @@ public T insert(T obj, String actived) { if (Strings.isBlank(actived)) return insert(obj); - + return insert(obj, FieldFilter.create(first.getClass(), actived)); } - + @Override public void truncate(Class klass) { Entity en = getEntity(klass); truncate(en.getTableName()); } - + @Override public void truncate(String tableName) { if (!exists(tableName)) diff --git a/src/org/nutz/dao/impl/NutTxDao.java b/src/org/nutz/dao/impl/NutTxDao.java index b7be170fa0..605dab5103 100644 --- a/src/org/nutz/dao/impl/NutTxDao.java +++ b/src/org/nutz/dao/impl/NutTxDao.java @@ -30,8 +30,6 @@ public class NutTxDao extends NutDao implements Closeable { protected String id; - protected Savepoint sp; - protected boolean debug; /** @@ -76,6 +74,7 @@ public void _run(DataSource dataSource, ConnCallback callback) { } } }); + sps = new NutMap(); } /** @@ -164,12 +163,7 @@ public NutTxDao rollback(String id) { if (debug) log.debugf("rollback id=%s", id); try { - Savepoint sp = null; - if (this.id.equals(id)) - sp = this.sp; - else if (sps != null) { - sp = sps.getAs(id, Savepoint.class); - } + Savepoint sp = sps.getAs(id, Savepoint.class); if (sp != null) conn.rollback(sp); else @@ -182,14 +176,7 @@ else if (sps != null) { public NutTxDao setSavepoint(String spId) { try { - Savepoint sp = conn.setSavepoint(spId); - if (id.equals(spId)) - this.sp = sp; - else { - if (sps == null) - sps = new NutMap(); - sps.put(spId, sp); - } + sps.put(spId, conn.setSavepoint()); } catch (SQLException e) { throw new DaoException(e); diff --git a/src/org/nutz/dao/impl/entity/AnnotationEntityMaker.java b/src/org/nutz/dao/impl/entity/AnnotationEntityMaker.java index 9c1daa038e..75c7c6c069 100644 --- a/src/org/nutz/dao/impl/entity/AnnotationEntityMaker.java +++ b/src/org/nutz/dao/impl/entity/AnnotationEntityMaker.java @@ -13,6 +13,7 @@ import javax.sql.DataSource; +import org.nutz.conf.NutConf; import org.nutz.dao.DB; import org.nutz.dao.DaoException; import org.nutz.dao.entity.Entity; @@ -46,6 +47,7 @@ import org.nutz.dao.impl.entity.info._Infos; import org.nutz.dao.impl.entity.macro.ElFieldMacro; import org.nutz.dao.impl.entity.macro.SqlFieldMacro; +import org.nutz.dao.interceptor.PojoInterceptor; import org.nutz.dao.jdbc.JdbcExpert; import org.nutz.dao.jdbc.Jdbcs; import org.nutz.dao.sql.Pojo; @@ -324,6 +326,12 @@ else if (mi.annName != null) { holder.remove(en); throw Lang.wrapThrow(e); } + // 处理Pojo拦截器 + if (NutConf.DAO_USE_POJO_INTERCEPTOR && ti.annTable != null) { + PojoInterceptor pint = Mirror.me(ti.annTable.interceptor()).born(); + pint.setupEntity(en, expert); + en.setInterceptor(pint); + } // 搞定收工,哦耶 ^_^ en.setComplete(true); @@ -442,7 +450,7 @@ private void _evalMappingField(NutMappingField ef, MappingInfo info) { // 用 @PK 的方式声明的主键 if (info.annPK.value().length == 1) { if (Lang.contains(info.annPK.value(), info.name)) { - if (ef.getTypeMirror().isIntLike()) + if (ef.getMirror().isIntLike()) ef.setAsId(); else ef.setAsName(); @@ -510,13 +518,14 @@ else if (Lang.contains(info.annPK.value(), info.name)) ef.setEjecting(info.ejecting); // 强制大小? - if (Daos.FORCE_UPPER_COLUMN_NAME) + if (Daos.FORCE_UPPER_COLUMN_NAME) { ef.setColumnName(ef.getColumnName().toUpperCase()); + } if (Daos.FORCE_WRAP_COLUMN_NAME || (info.annColumn != null && info.annColumn.wrap())) { - ef.setColumnNameInSql(expert.wrapKeywork(columnName, true)); + ef.setColumnNameInSql(expert.wrapKeyword(columnName, true)); } else if (Daos.CHECK_COLUMN_NAME_KEYWORD) { - ef.setColumnNameInSql(expert.wrapKeywork(columnName, false)); + ef.setColumnNameInSql(expert.wrapKeyword(columnName, false)); } } @@ -587,6 +596,16 @@ private void _evalEntityIndexes(NutEntity en, TableIndexes indexes) { } en.addIndex(index); } + for (Field field : en.getMirror().getFields()) { + Index idx = field.getAnnotation(Index.class); + if (idx == null) + continue; + NutEntityIndex index = new NutEntityIndex(); + index.setUnique(idx.unique()); + index.setName(idx.name()); + index.addField(en.getField(field.getName())); + en.addIndex(index); + } } private void _checkupEntityFieldsWithDatabase(NutEntity en) { diff --git a/src/org/nutz/dao/impl/entity/MapEntityMaker.java b/src/org/nutz/dao/impl/entity/MapEntityMaker.java index a47e8dc1fd..19e12d3fc7 100644 --- a/src/org/nutz/dao/impl/entity/MapEntityMaker.java +++ b/src/org/nutz/dao/impl/entity/MapEntityMaker.java @@ -1,12 +1,5 @@ package org.nutz.dao.impl.entity; -import java.sql.Connection; -import java.sql.SQLException; -import java.util.Map; -import java.util.Map.Entry; - -import javax.sql.DataSource; - import org.nutz.dao.entity.Entity; import org.nutz.dao.entity.annotation.ColType; import org.nutz.dao.impl.EntityHolder; @@ -14,12 +7,19 @@ import org.nutz.dao.jdbc.JdbcExpert; import org.nutz.dao.jdbc.Jdbcs; import org.nutz.dao.jdbc.ValueAdaptor; +import org.nutz.dao.util.Daos; import org.nutz.lang.Mirror; import org.nutz.lang.eject.EjectFromMap; import org.nutz.lang.inject.InjectToMap; import org.nutz.log.Log; import org.nutz.log.Logs; +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Map; +import java.util.Map.Entry; + public class MapEntityMaker { private static final Log log = Logs.get(); @@ -30,9 +30,18 @@ public class MapEntityMaker { @SuppressWarnings({"unchecked", "rawtypes"}) public > Entity make(String tableName, T map) { + return this.make(tableName, map, false, false, null); + } + + public > Entity make(String tableName, T map, boolean hasColumnComment, boolean hasTableComment, String tableComment) { final NutEntity en = new NutEntity(map.getClass()); en.setTableName(tableName); en.setViewName(tableName); + en.setHasColumnComment(hasColumnComment); + en.setHasTableComment(hasTableComment); + if (tableComment != null) { + en.setTableComment(tableComment); + } boolean check = false; for (Entry entry : map.entrySet()) { String key = entry.getKey(); @@ -50,7 +59,7 @@ else if (key.startsWith(".")) { Object value = entry.getValue(); Mirror mirror = Mirror.me(value); NutMappingField ef = new NutMappingField(en); - + ef.setHasColumnComment(hasColumnComment); while (true) { if (key.startsWith("+")) { ef.setAsAutoIncreasement(); @@ -71,7 +80,19 @@ else if (key.startsWith(".")) { } } ef.setName(key); - ef.setColumnName(key); + String columnName = key; + // 强制大写? + if (Daos.FORCE_UPPER_COLUMN_NAME) { + ef.setColumnName(columnName.toUpperCase()); + } else { + ef.setColumnName(columnName); + } + // 强制包裹? + if (Daos.FORCE_WRAP_COLUMN_NAME) { + ef.setColumnNameInSql(expert.wrapKeyword(columnName, true)); + } else if (Daos.CHECK_COLUMN_NAME_KEYWORD) { + ef.setColumnNameInSql(expert.wrapKeyword(columnName, false)); + } // 类型是啥呢? if (map.containsKey("." + key + ".type")) { @@ -88,17 +109,36 @@ else if (key.startsWith(".")) { } // 适配器类型是啥呢? if (map.containsKey("." + key + ".adaptor")) { - ef.setAdaptor((ValueAdaptor) map.get("." + key + ".adapter")); + ef.setAdaptor((ValueAdaptor) map.get("." + key + ".adaptor")); } else { ef.setAdaptor(expert.getAdaptor(ef)); } + + // 字段长度是多少呢 + if (map.containsKey("." + key + ".width")) { + Object w = map.get("." + key + ".width"); + if (null != w && (w instanceof Integer)) { + ef.setWidth((Integer) w); + } + } + + // 字段备注 + if (map.containsKey("." + key + ".comment")) { + ef.setColumnComment((String) map.get("." + key + ".comment")); + } + + // 自定义字段类型 + if (map.containsKey("." + key + ".customtype")) { + ef.setCustomDbType((String) map.get("." + key + ".customtype")); + } + ef.setInjecting(new InjectToMap(key)); // 这里比较纠结,回设的时候应该用什么呢? ef.setEjecting(new EjectFromMap(entry.getKey())); if (ef.isAutoIncreasement() - && ef.isId() - && expert.isSupportAutoIncrement() - && !expert.isSupportGeneratedKeys()) { + && ef.isId() + && expert.isSupportAutoIncrement() + && !expert.isSupportGeneratedKeys()) { en.addAfterInsertMacro(expert.fetchPojoId(en, ef)); } @@ -116,13 +156,11 @@ else if (key.startsWith(".")) { try { conn = dataSource.getConnection(); expert.setupEntityField(conn, en); - } - finally { + } finally { if (conn != null) conn.close(); } - } - catch (SQLException e) { + } catch (SQLException e) { log.debug(e.getMessage(), e); } } diff --git a/src/org/nutz/dao/impl/entity/NutEntity.java b/src/org/nutz/dao/impl/entity/NutEntity.java index 49c073ee80..bfbbe7cdee 100644 --- a/src/org/nutz/dao/impl/entity/NutEntity.java +++ b/src/org/nutz/dao/impl/entity/NutEntity.java @@ -1,8 +1,21 @@ package org.nutz.dao.impl.entity; +import java.sql.ResultSet; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import org.nutz.dao.DaoException; import org.nutz.dao.FieldMatcher; -import org.nutz.dao.entity.*; +import org.nutz.dao.entity.Entity; +import org.nutz.dao.entity.EntityIndex; +import org.nutz.dao.entity.LinkField; +import org.nutz.dao.entity.LinkVisitor; +import org.nutz.dao.entity.MappingField; +import org.nutz.dao.entity.PkType; +import org.nutz.dao.entity.Record; +import org.nutz.dao.interceptor.PojoInterceptor; import org.nutz.dao.sql.Pojo; import org.nutz.lang.Lang; import org.nutz.lang.Mirror; @@ -11,12 +24,6 @@ import org.nutz.lang.born.Borns; import org.nutz.lang.util.Context; -import java.sql.ResultSet; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - /** * 记录一个实体 * @@ -155,9 +162,11 @@ public class NutEntity implements Entity { * 实体的主键类型 */ private PkType pkType; - + private boolean complete; + private PojoInterceptor interceptor; + public NutEntity(final Class type) { this.type = type; this.mirror = Mirror.me(type); @@ -194,7 +203,7 @@ else if (null == bornByDefault) this.manys = new LinkFieldSet(); this.manymanys = new LinkFieldSet(); } - + public T getObject(ResultSet rs, FieldMatcher matcher) { return getObject(rs, matcher, null); } @@ -217,7 +226,7 @@ public T getObject(ResultSet rs, FieldMatcher matcher, String prefix) { // 返回构造的对象 return re; } - + public T getObject(Record rec) { return getObject(rec, null); } @@ -265,10 +274,11 @@ public void addMappingField(MappingField field) { theId = field; else if (field.isName()) theName = field; - //wjw(2017-04-10),add,乐观锁 + // wjw(2017-04-10),add,乐观锁 else if (field.isVersion()) - theVersion =field; - + theVersion = field; + + field.setEntity(this); byJava.put(field.getName(), field); byDB.put(field.getColumnName(), field); fields.add(field); @@ -386,7 +396,7 @@ public MappingField getNameField() { } public MappingField getVersionField() { - return this.theVersion; + return this.theVersion; } public MappingField getIdField() { @@ -498,10 +508,22 @@ public boolean isComplete() { public void setComplete(boolean complete) { this.complete = complete; } - + public T born(ResultSet rs) { if (null != bornByRS) return bornByRS.born(rs); return bornByDefault.born(EMTRY_ARG); } + + public PojoInterceptor getInterceptor() { + return this.interceptor; + } + + public void setInterceptor(PojoInterceptor interceptor) { + this.interceptor = interceptor; + } + + public boolean hasInsertMacroes() { + return beforeInsertMacroes.size() > 0 || afterInsertMacroes.size() > 0; + } } diff --git a/src/org/nutz/dao/impl/entity/NutEntityIndex.java b/src/org/nutz/dao/impl/entity/NutEntityIndex.java index 4bb0222460..8ba431d8da 100644 --- a/src/org/nutz/dao/impl/entity/NutEntityIndex.java +++ b/src/org/nutz/dao/impl/entity/NutEntityIndex.java @@ -28,7 +28,7 @@ public boolean isUnique() { public void setUnique(boolean unique) { this.unique = unique; } - + public String getName() { return name; } @@ -59,4 +59,8 @@ public List getFields() { return fields; } + public void setFields(List fields) { + this.fields = fields; + } + } diff --git a/src/org/nutz/dao/impl/entity/field/AbstractEntityField.java b/src/org/nutz/dao/impl/entity/field/AbstractEntityField.java index d5a597d076..58b7b8cb3f 100644 --- a/src/org/nutz/dao/impl/entity/field/AbstractEntityField.java +++ b/src/org/nutz/dao/impl/entity/field/AbstractEntityField.java @@ -11,19 +11,19 @@ public abstract class AbstractEntityField implements EntityField { - private Entity entity; + protected Entity entity; - private String name; + protected String name; - private Type type; + protected Type type; - private Class typeClass; + protected Class typeClass; - private Mirror mirror; + protected Mirror mirror; - private Injecting injecting; + protected Injecting injecting; - private Ejecting ejecting; + protected Ejecting ejecting; public AbstractEntityField(Entity entity) { this.entity = entity; @@ -45,7 +45,7 @@ public Class getTypeClass() { return typeClass; } - public Mirror getTypeMirror() { + public Mirror getMirror() { return mirror; } @@ -75,6 +75,18 @@ public void setType(Type type) { this.mirror = Mirror.me(typeClass); } + public void setEntity(Entity entity) { + this.entity = entity; + } + + public void setTypeClass(Class typeClass) { + this.typeClass = typeClass; + } + + public void setMirror(Mirror mirror) { + this.mirror = mirror; + } + public String toString() { return String.format("'%s'(%s)", this.name, this.entity.getType().getName()); } diff --git a/src/org/nutz/dao/impl/entity/field/AbstractLinkField.java b/src/org/nutz/dao/impl/entity/field/AbstractLinkField.java index 44ae8f5fd2..e3b72ec914 100644 --- a/src/org/nutz/dao/impl/entity/field/AbstractLinkField.java +++ b/src/org/nutz/dao/impl/entity/field/AbstractLinkField.java @@ -42,9 +42,9 @@ public AbstractLinkField(Entity entity, EntityHolder holder, LinkInfo info) { this.setType(info.fieldType); - if (getTypeMirror().isOf(Collection.class)) { + if (getMirror().isOf(Collection.class)) { callback = new PojoQueryEntityCallback(); - } else if (getTypeMirror().isOf(Map.class)) { + } else if (getMirror().isOf(Map.class)) { callback = new PojoQueryEntityCallback(); } else if (getTypeClass().isArray()) { callback = new PojoQueryEntityCallback(); diff --git a/src/org/nutz/dao/impl/entity/field/ManyLinkField.java b/src/org/nutz/dao/impl/entity/field/ManyLinkField.java index 3d2467215c..0e4def351e 100644 --- a/src/org/nutz/dao/impl/entity/field/ManyLinkField.java +++ b/src/org/nutz/dao/impl/entity/field/ManyLinkField.java @@ -48,7 +48,7 @@ public ManyLinkField(Entity entity, EntityHolder holder, LinkInfo info) { } // 宿主实体的字段 - 应该是主键 - boolean intLike = linkedField.getTypeMirror().isIntLike(); + boolean intLike = linkedField.getMirror().isIntLike(); if (Strings.isBlank(mapKey) || Mirror.me(info.fieldType).isMap()) { hostField = intLike ? getEntity().getIdField() : getEntity().getNameField(); diff --git a/src/org/nutz/dao/impl/entity/field/NutMappingField.java b/src/org/nutz/dao/impl/entity/field/NutMappingField.java index 9d7dda7c95..7e6d6029b3 100644 --- a/src/org/nutz/dao/impl/entity/field/NutMappingField.java +++ b/src/org/nutz/dao/impl/entity/field/NutMappingField.java @@ -15,264 +15,340 @@ public class NutMappingField extends AbstractEntityField implements MappingField { - private String columnName; - - private String columnNameInSql; + private String columnName; - private ColType columnType; + private String columnNameInSql; - private Segment defaultValue; + private ColType columnType; - private String columnComment; + private Segment defaultValue; - private int width; + private String columnComment; - private int precision; + private int width; - private boolean isCompositePk; + private int precision; - private boolean isId; + private boolean isCompositePk; - private boolean isName; + private boolean isId; - private boolean isVersion; + private boolean isName; - private boolean readonly; + private boolean isVersion; - private boolean notNull; + private boolean readonly; - private boolean unsigned; + private boolean notNull; - private boolean autoIncreasement; + private boolean unsigned; - private boolean casesensitive; + private boolean autoIncreasement; - private boolean hasColumnComment; + private boolean casesensitive; - private String customDbType; + private boolean hasColumnComment; - private ValueAdaptor adaptor; + private String customDbType; - private boolean insert = true; + private ValueAdaptor adaptor; - private boolean update = true; - - private static final Log log = Logs.get(); + private boolean insert = true; - public NutMappingField(Entity entity) { - super(entity); - casesensitive = true; - } + private boolean update = true; - public ValueAdaptor getAdaptor() { - return adaptor; - } + private static final Log log = Logs.get(); - public void setAdaptor(ValueAdaptor adaptor) { - this.adaptor = adaptor; - } + public NutMappingField() { + this(null); + } + + public NutMappingField(Entity entity) { + super(entity); + casesensitive = true; + } + + public ValueAdaptor getAdaptor() { + return adaptor; + } + + public void setAdaptor(ValueAdaptor adaptor) { + this.adaptor = adaptor; + } - public void injectValue(Object obj, Record rec, String prefix) { - try { - Object val = rec.get(prefix == null ? columnName : prefix + columnName); - this.setValue(obj, val); - } - catch (Exception e) { + public void injectValue(Object obj, Record rec, String prefix) { + try { + Object val = rec.get(prefix == null ? columnName : prefix + columnName); + this.setValue(obj, val); + } + catch (Exception e) { if (log.isTraceEnabled()) { - log.tracef("columnName="+columnName, e); + log.tracef("columnName=" + columnName, e); } - } - } - - public void injectValue(Object obj, ResultSet rs, String prefix) { - try { - this.setValue(obj, adaptor.get(rs, prefix == null ? columnName : prefix + columnName)); - } - catch (SQLException e) { - if (log.isTraceEnabled()) { - log.tracef("columnName="+columnName, e); - } - } - } - - public String getColumnName() { - return columnName; - } - - public ColType getColumnType() { - return columnType; - } - - public String getDefaultValue(Object obj) { - if (null == defaultValue) - return null; - String re; - if (null == obj || defaultValue.keyCount() == 0) - re = defaultValue.toString(); - else - re = defaultValue.render(new EntityObjectContext(getEntity(), obj)).toString(); - return re; - } - - public int getWidth() { - return width; - } - - public int getPrecision() { - return precision; - } - - public boolean isCompositePk() { - return isCompositePk; - } - - public boolean isPk() { - return isId || (!isId && isName) || isCompositePk; - } - - public boolean isId() { - return isId; - } - - public boolean isName() { - return isName; - } - - public boolean isReadonly() { - return readonly; - } - - public boolean hasDefaultValue() { - return null != defaultValue; - } - - public boolean isNotNull() { - return notNull; - } - - public boolean isCasesensitive() { - return casesensitive; - } - - public boolean isAutoIncreasement() { - return autoIncreasement; - } - - public boolean isUnsigned() { - return unsigned; - } - - public void setColumnName(String columnName) { - this.columnName = columnName; - } - - public void setColumnType(ColType columnType) { - this.columnType = columnType; - } - - public void setColumnComment(String columnComment) { - this.columnComment = columnComment; - } - - public void setHasColumnComment(boolean hasColumnComment) { - this.hasColumnComment = hasColumnComment; - } - - public void setDefaultValue(Segment defaultValue) { - this.defaultValue = defaultValue; - } - - public void setWidth(int width) { - this.width = width; - } - - public void setPrecision(int precision) { - this.precision = precision; - } - - public void setAsCompositePk() { - this.isCompositePk = true; - } - - public void setAsId() { - this.isId = true; - } - - public void setAsName() { - this.isName = true; - } - - public void setAsReadonly() { - this.readonly = true; - } - - public void setAsNotNull() { - this.notNull = true; - } - - public void setAsUnsigned() { - this.unsigned = true; - } + } + } + + public void injectValue(Object obj, ResultSet rs, String prefix) { + try { + this.setValue(obj, adaptor.get(rs, prefix == null ? columnName : prefix + columnName)); + } + catch (SQLException e) { + if (log.isTraceEnabled()) { + log.tracef("columnName=" + columnName, e); + } + } + } + + public String getColumnName() { + return columnName; + } - public void setCasesensitive(boolean casesensitive) { - this.casesensitive = casesensitive; - } + public ColType getColumnType() { + return columnType; + } + + public String getDefaultValue(Object obj) { + if (null == defaultValue) + return null; + String re; + if (null == obj || defaultValue.keyCount() == 0) + re = defaultValue.toString(); + else + re = defaultValue.render(new EntityObjectContext(getEntity(), obj)).toString(); + return re; + } + + public int getWidth() { + return width; + } + + public int getPrecision() { + return precision; + } + + public boolean isCompositePk() { + return isCompositePk; + } + + public boolean isPk() { + return isId || (!isId && isName) || isCompositePk; + } + + public boolean isId() { + return isId; + } + + public boolean isName() { + return isName; + } + + public boolean isReadonly() { + return readonly; + } + + public boolean hasDefaultValue() { + return null != defaultValue; + } + + public boolean isNotNull() { + return notNull; + } + + public boolean isCasesensitive() { + return casesensitive; + } + + public boolean isAutoIncreasement() { + return autoIncreasement; + } + + public boolean isUnsigned() { + return unsigned; + } + + public void setColumnName(String columnName) { + this.columnName = columnName; + } + + public void setColumnType(ColType columnType) { + this.columnType = columnType; + } + + public void setColumnComment(String columnComment) { + this.columnComment = columnComment; + } + + public void setHasColumnComment(boolean hasColumnComment) { + this.hasColumnComment = hasColumnComment; + } + + public void setDefaultValue(Segment defaultValue) { + this.defaultValue = defaultValue; + } + + public void setWidth(int width) { + this.width = width; + } - public void setAsAutoIncreasement() { - this.autoIncreasement = true; - } - - public void setAutoIncreasement(boolean autoIncreasement) { + public void setPrecision(int precision) { + this.precision = precision; + } + + public void setAsCompositePk() { + this.isCompositePk = true; + } + + public void setAsId() { + this.isId = true; + } + + public void setAsName() { + this.isName = true; + } + + public void setAsReadonly() { + this.readonly = true; + } + + public void setAsNotNull() { + this.notNull = true; + } + + public void setAsUnsigned() { + this.unsigned = true; + } + + public void setCasesensitive(boolean casesensitive) { + this.casesensitive = casesensitive; + } + + public void setAsAutoIncreasement() { + this.autoIncreasement = true; + } + + public void setAutoIncreasement(boolean autoIncreasement) { this.autoIncreasement = autoIncreasement; } - public String getColumnComment() { - return columnComment; - } + public String getColumnComment() { + return columnComment; + } - public boolean hasColumnComment() { - return hasColumnComment; - } + public boolean hasColumnComment() { + return hasColumnComment; + } - public void setCustomDbType(String customDbType) { - this.customDbType = customDbType; - } + public void setCustomDbType(String customDbType) { + this.customDbType = customDbType; + } + + public String getCustomDbType() { + return customDbType; + } - public String getCustomDbType() { - return customDbType; - } + public boolean isInsert() { + return insert; + } - public boolean isInsert() { - return insert; - } + public boolean isUpdate() { + return update; + } - public boolean isUpdate() { - return update; - } + public void setInsert(boolean insert) { + this.insert = insert; + } - public void setInsert(boolean insert) { - this.insert = insert; - } + public void setUpdate(boolean update) { + this.update = update; + } - public void setUpdate(boolean update) { - this.update = update; - } + public String getColumnNameInSql() { + if (columnNameInSql != null) + return columnNameInSql; + return columnName; + } - public String getColumnNameInSql() { - if (columnNameInSql != null) - return columnNameInSql; - return columnName; - } - - public void setColumnNameInSql(String columnNameInSql) { + public void setColumnNameInSql(String columnNameInSql) { this.columnNameInSql = columnNameInSql; } - public boolean isVersion() { - return isVersion; - } + public boolean isVersion() { + return isVersion; + } + + public void setAsVersion() { + this.isVersion = true; + } + + // 补充一下 Setter/Getter + + public Segment getDefaultValue() { + return defaultValue; + } + + public boolean isHasColumnComment() { + return hasColumnComment; + } + + public void setCompositePk(boolean isCompositePk) { + this.isCompositePk = isCompositePk; + } + + public void setId(boolean isId) { + this.isId = isId; + } + + public void setName(boolean isName) { + this.isName = isName; + } + + public void setVersion(boolean isVersion) { + this.isVersion = isVersion; + } + + public void setReadonly(boolean readonly) { + this.readonly = readonly; + } + + public void setNotNull(boolean notNull) { + this.notNull = notNull; + } + + public void setUnsigned(boolean unsigned) { + this.unsigned = unsigned; + } + + public NutMappingField clone() { + NutMappingField mf = new NutMappingField(entity); + mf.entity = this.entity; + mf.name = this.name; + mf.type = this.type; + mf.typeClass = this.typeClass; + mf.mirror = this.mirror; + mf.injecting = this.injecting; + mf.ejecting = this.ejecting; + mf.columnName = this.columnName; + mf.columnNameInSql = this.columnNameInSql; + mf.columnType = this.columnType; + mf.defaultValue = this.defaultValue; + mf.columnComment = this.columnComment; + mf.width = this.width; + mf.precision = this.precision; + mf.isCompositePk = this.isCompositePk; + mf.isId = this.isId; + mf.isName = this.isName; + mf.isVersion = this.isVersion; + mf.readonly = this.readonly; + mf.notNull = this.notNull; + mf.unsigned = this.unsigned; + mf.autoIncreasement = this.autoIncreasement; + mf.casesensitive = this.casesensitive; + mf.hasColumnComment = this.hasColumnComment; + mf.customDbType = this.customDbType; + mf.adaptor = this.adaptor; + mf.insert = this.insert; + mf.update = this.update; + return mf; + } - public void setAsVersion() { - this.isVersion = true; - } } diff --git a/src/org/nutz/dao/impl/entity/field/OneLinkField.java b/src/org/nutz/dao/impl/entity/field/OneLinkField.java index 18489cba0b..77730a740a 100644 --- a/src/org/nutz/dao/impl/entity/field/OneLinkField.java +++ b/src/org/nutz/dao/impl/entity/field/OneLinkField.java @@ -54,7 +54,7 @@ public OneLinkField(Entity entity, EntityHolder holder, LinkInfo info) { } // 链接实体的字段 - linkedField = hostField.getTypeMirror().isIntLike() ? this.getLinkedEntity().getIdField() + linkedField = hostField.getMirror().isIntLike() ? this.getLinkedEntity().getIdField() : this.getLinkedEntity().getNameField(); if (null == linkedField) throw Lang.makeThrow("Fail to find linkedField for @One(field=%s) '%s' : %s<=>%s By %s", @@ -62,7 +62,7 @@ public OneLinkField(Entity entity, EntityHolder holder, LinkInfo info) { getName(), getEntity().getType(), getLinkedEntity().getType(), - hostField.getTypeMirror().isIntLike() ? "@Id" : "@Name"); + hostField.getMirror().isIntLike() ? "@Id" : "@Name"); } diff --git a/src/org/nutz/dao/impl/jdbc/AbstractJdbcExpert.java b/src/org/nutz/dao/impl/jdbc/AbstractJdbcExpert.java index 4ed51b9bdc..894c30bd86 100644 --- a/src/org/nutz/dao/impl/jdbc/AbstractJdbcExpert.java +++ b/src/org/nutz/dao/impl/jdbc/AbstractJdbcExpert.java @@ -28,15 +28,19 @@ import org.nutz.dao.jdbc.Jdbcs; import org.nutz.dao.jdbc.ValueAdaptor; import org.nutz.dao.sql.DaoStatement; +import org.nutz.dao.sql.PItem; import org.nutz.dao.sql.Pojo; import org.nutz.dao.sql.Sql; import org.nutz.dao.sql.SqlContext; import org.nutz.dao.sql.SqlType; import org.nutz.dao.util.Daos; +import org.nutz.dao.util.Pojos; +import org.nutz.lang.Configurable; import org.nutz.lang.Lang; import org.nutz.lang.Mirror; import org.nutz.lang.Strings; import org.nutz.lang.segment.CharSegment; +import org.nutz.lang.util.NutMap; import org.nutz.log.Log; import org.nutz.log.Logs; @@ -45,7 +49,7 @@ * * @author zozoh(zozohtnt@gmail.com) */ -public abstract class AbstractJdbcExpert implements JdbcExpert { +public abstract class AbstractJdbcExpert implements JdbcExpert, Configurable { private static final Log log = Logs.get(); @@ -72,7 +76,7 @@ public AbstractJdbcExpert(JdbcExpertConfigFile conf) { public void setupEntityField(Connection conn, Entity en) { List mfs = new ArrayList(); for (MappingField mf : en.getMappingFields()) { - if (mf.getTypeMirror().isEnum()) { + if (mf.getMirror().isEnum()) { mfs.add(mf); } } @@ -123,7 +127,7 @@ public void setupEntityField(Connection conn, Entity en) { } public ValueAdaptor getAdaptor(MappingField ef) { - Mirror mirror = ef.getTypeMirror(); + Mirror mirror = ef.getMirror(); // 为数字型枚举的特殊判断 if (mirror.isEnum() && ColType.INT == ef.getColumnType()) return Jdbcs.Adaptor.asEnumInt; @@ -238,10 +242,13 @@ public String evalFieldType(MappingField mf) { return "NUMERIC(" + mf.getWidth() + "," + mf.getPrecision() + ")"; } // 用默认精度 - if (mf.getTypeMirror().isDouble()) + if (mf.getMirror().isDouble()) return "NUMERIC(15,10)"; return "FLOAT"; + case DOUBLE: + return "DOUBLE"; + case PSQL_ARRAY: return "ARRAY"; @@ -256,7 +263,7 @@ public String evalFieldType(MappingField mf) { } } - protected static List wrap(String... sqls) { + protected static List getNumberOfRecords(String... sqls) { List sts = new ArrayList(sqls.length); for (String sql : sqls) if (!Strings.isBlank(sql)) @@ -338,7 +345,7 @@ public void addComment(Dao dao, Entity en, String commentTable, String commen : commentColumn); columnCommentSQL.vars() .set("table", en.getTableName()) - .set("column", mf.getColumnName()) + .set("column", mf.getColumnNameInSql()) .set("columnComment", mf.getColumnComment()); sqls.add(columnCommentSQL); } @@ -397,7 +404,7 @@ public void addDefaultValue(StringBuilder sb, MappingField mf) { return; String dft = getDefaultValue(mf); if (mf.getColumnType() == ColType.VARCHAR - || mf.getTypeMirror().isStringLike()) + || mf.getMirror().isStringLike()) sb.append(" DEFAULT '").append(dft).append('\''); else sb.append(" DEFAULT ").append(dft); @@ -419,7 +426,7 @@ public Set getKeywords() { return keywords; } - public String wrapKeywork(String columnName, boolean force) { + public String wrapKeyword(String columnName, boolean force) { if (force || keywords.contains(columnName.toUpperCase())) return "`" + columnName + "`"; return null; @@ -481,4 +488,29 @@ public List getIndexNames(Entity en, Connection conn) throws SQLExcep } return names; } + + public void setupProperties(NutMap conf) { + } + + @Override + public PItem formatLeftJoinLink(Object obj, LinkField lnk, Entity en) { + Entity lnkEntity = lnk.getLinkedEntity(); + String linkName = safeTableName(lnk.getName()); + String LJ = String.format("LEFT JOIN %s as %s ON %s.%s = %s.%s", + lnkEntity.getTableName(), + linkName, + en.getTableName(), + lnk.getHostField().getColumnNameInSql(), + linkName, + lnk.getLinkedField().getColumnNameInSql()); + return Pojos.Items.wrap(LJ); + } + + protected String safeTableName(String tableName) { + if (!Daos.CHECK_COLUMN_NAME_KEYWORD) { + //return tableName; + } + String str = wrapKeyword(tableName, Daos.FORCE_WRAP_COLUMN_NAME); + return str == null ? tableName : str; + } } diff --git a/src/org/nutz/dao/impl/jdbc/BlobValueAdaptor.java b/src/org/nutz/dao/impl/jdbc/BlobValueAdaptor.java index 554ddaae84..43dd650b11 100644 --- a/src/org/nutz/dao/impl/jdbc/BlobValueAdaptor.java +++ b/src/org/nutz/dao/impl/jdbc/BlobValueAdaptor.java @@ -27,6 +27,15 @@ public Object get(ResultSet rs, String colName) throws SQLException { return new SimpleBlob(f); } + public Object get(ResultSet rs, int columnIndex) throws SQLException { + Blob blob = rs.getBlob(columnIndex); + if (blob == null) + return null; + File f = this.createTempFile(); + Files.write(f, blob.getBinaryStream()); + return new SimpleBlob(f); + } + public void set(PreparedStatement stat, Object obj, int i) throws SQLException { if (null == obj) { stat.setNull(i, Types.BLOB); diff --git a/src/org/nutz/dao/impl/jdbc/BlobValueAdaptor3.java b/src/org/nutz/dao/impl/jdbc/BlobValueAdaptor3.java new file mode 100644 index 0000000000..94f513e082 --- /dev/null +++ b/src/org/nutz/dao/impl/jdbc/BlobValueAdaptor3.java @@ -0,0 +1,28 @@ +package org.nutz.dao.impl.jdbc; + +import java.io.File; +import java.io.InputStream; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.nutz.dao.util.blob.SimpleBlob; +import org.nutz.filepool.FilePool; +import org.nutz.lang.Files; + +public class BlobValueAdaptor3 extends BlobValueAdaptor2 { + + public BlobValueAdaptor3(FilePool pool) { + super(pool); + } + + @Override + public Object get(ResultSet rs, String colName) throws SQLException { + InputStream ins = rs.getBinaryStream(colName); + if (ins == null) + return null; + File f = this.createTempFile(); + Files.write(f, ins); + return new SimpleBlob(f); + } + +} diff --git a/src/org/nutz/dao/impl/jdbc/ClobValueAdaptor.java b/src/org/nutz/dao/impl/jdbc/ClobValueAdaptor.java index f6e4fa8aa6..c4f4cf3ab8 100644 --- a/src/org/nutz/dao/impl/jdbc/ClobValueAdaptor.java +++ b/src/org/nutz/dao/impl/jdbc/ClobValueAdaptor.java @@ -27,6 +27,15 @@ public Object get(ResultSet rs, String colName) throws SQLException { return new SimpleClob(f); } + public Object get(ResultSet rs, int columnIndex) throws SQLException { + Clob clob = rs.getClob(columnIndex); + if (clob == null) + return null; + File f = this.createTempFile(); + Streams.writeAndClose(Streams.fileOutw(f), clob.getCharacterStream()); + return new SimpleClob(f); + } + public void set(PreparedStatement stat, Object obj, int i) throws SQLException { if (null == obj) { stat.setNull(i, Types.CLOB); diff --git a/src/org/nutz/dao/impl/jdbc/clickhouse/ClickhouseJdbcExpert.java b/src/org/nutz/dao/impl/jdbc/clickhouse/ClickhouseJdbcExpert.java new file mode 100644 index 0000000000..be4a0da889 --- /dev/null +++ b/src/org/nutz/dao/impl/jdbc/clickhouse/ClickhouseJdbcExpert.java @@ -0,0 +1,18 @@ +package org.nutz.dao.impl.jdbc.clickhouse; + +import java.sql.Connection; +import java.sql.SQLException; + +import org.nutz.dao.impl.jdbc.mysql.MysqlJdbcExpert; +import org.nutz.dao.jdbc.JdbcExpertConfigFile; + +public class ClickhouseJdbcExpert extends MysqlJdbcExpert { + + public ClickhouseJdbcExpert(JdbcExpertConfigFile conf) { + super(conf); + } + + @Override + public void checkDataSource(Connection conn) throws SQLException {} + +} diff --git a/src/org/nutz/dao/impl/jdbc/db2/Db2JdbcExpert.java b/src/org/nutz/dao/impl/jdbc/db2/Db2JdbcExpert.java index 23a6625053..350bcd829d 100644 --- a/src/org/nutz/dao/impl/jdbc/db2/Db2JdbcExpert.java +++ b/src/org/nutz/dao/impl/jdbc/db2/Db2JdbcExpert.java @@ -108,7 +108,7 @@ public String evalFieldType(MappingField mf) { return "decimal(" + mf.getWidth() + "," + mf.getPrecision() + ")"; } // 用默认精度 - if (mf.getTypeMirror().isDouble()) + if (mf.getMirror().isDouble()) return "decimal(15,10)"; return "FLOAT"; case TIMESTAMP: @@ -146,7 +146,7 @@ public void formatQuery(Sql sql) { } public ValueAdaptor getAdaptor(MappingField ef) { - if (ef.getTypeMirror().isBoolean()) + if (ef.getMirror().isBoolean()) return new DB2BooleanAdaptor(); return super.getAdaptor(ef); } diff --git a/src/org/nutz/dao/impl/jdbc/derby/DerbyJdbcExpert.java b/src/org/nutz/dao/impl/jdbc/derby/DerbyJdbcExpert.java index 68aebb81fc..defbb64b77 100644 --- a/src/org/nutz/dao/impl/jdbc/derby/DerbyJdbcExpert.java +++ b/src/org/nutz/dao/impl/jdbc/derby/DerbyJdbcExpert.java @@ -30,7 +30,7 @@ public String getDatabaseType() { @Override public ValueAdaptor getAdaptor(MappingField ef) { - if (ef.getTypeMirror().isBoolean()) + if (ef.getMirror().isBoolean()) return new DerbyBooleanAdaptor(); return super.getAdaptor(ef); } diff --git a/src/org/nutz/dao/impl/jdbc/dm/DmMysqlJdbcExpert.java b/src/org/nutz/dao/impl/jdbc/dm/DmMysqlJdbcExpert.java new file mode 100644 index 0000000000..1591c445cb --- /dev/null +++ b/src/org/nutz/dao/impl/jdbc/dm/DmMysqlJdbcExpert.java @@ -0,0 +1,164 @@ +package org.nutz.dao.impl.jdbc.dm; + +import org.nutz.dao.DB; +import org.nutz.dao.Dao; +import org.nutz.dao.Sqls; +import org.nutz.dao.entity.Entity; +import org.nutz.dao.entity.LinkField; +import org.nutz.dao.entity.MappingField; +import org.nutz.dao.entity.PkType; +import org.nutz.dao.entity.annotation.ColType; +import org.nutz.dao.impl.jdbc.AbstractJdbcExpert; +import org.nutz.dao.jdbc.JdbcExpertConfigFile; +import org.nutz.dao.pager.Pager; +import org.nutz.dao.sql.Pojo; +import org.nutz.dao.sql.Sql; +import org.nutz.dao.util.Pojos; +import org.nutz.log.Log; +import org.nutz.log.Logs; + +import java.util.List; + +/** + * @author wizzer.cn + */ +public class DmMysqlJdbcExpert extends AbstractJdbcExpert { + + private static final Log log = Logs.get(); + + public DmMysqlJdbcExpert(JdbcExpertConfigFile conf) { + super(conf); + } + + public String getDatabaseType() { + return DB.DM_MYSQL.name(); + } + + public void formatQuery(Pojo pojo) { + Pager pager = pojo.getContext().getPager(); + // 需要进行分页 + if (null != pager && pager.getPageNumber() > 0) + pojo.append(Pojos.Items.wrapf(" LIMIT %d, %d", pager.getOffset(), pager.getPageSize())); + } + + public void formatQuery(Sql sql) { + Pager pager = sql.getContext().getPager(); + // 需要进行分页 + if (null != pager && pager.getPageNumber() > 0) + sql.setSourceSql(sql.getSourceSql() + + String.format(" LIMIT %d, %d", + pager.getOffset(), + pager.getPageSize())); + } + + public String evalFieldType(MappingField mf) { + if (mf.getCustomDbType() != null) + return mf.getCustomDbType(); + if (mf.getColumnType() == ColType.INT) { + int width = mf.getWidth(); + if (width <= 0) { + return "INT"; + } else if (width <= 4) { + return "TINYINT"; + } else if (width <= 6) { + return "SMALLINT"; + } else if (width <= 10) { + return "INT"; + } + return "BIGINT"; + } + if (mf.getColumnType() == ColType.BOOLEAN) { + return "TINYINT"; + } + // 其它的参照默认字段规则 ... + return super.evalFieldType(mf); + } + + public boolean createEntity(Dao dao, Entity en) { + StringBuilder sb = new StringBuilder("CREATE TABLE " + en.getTableName() + "("); + // 创建字段 + for (MappingField mf : en.getMappingFields()) { + if (mf.isReadonly()) + continue; + sb.append('\n').append(mf.getColumnNameInSql()); + sb.append(' ').append(evalFieldType(mf)); + // 非主键的 @Name,应该加入唯一性约束 + if (mf.isName() && en.getPkType() != PkType.NAME) { + sb.append(" UNIQUE NOT NULL"); + } + // 普通字段 + else { + + if (mf.isNotNull()) { + sb.append(" NOT NULL"); + } else if (mf.getColumnType() == ColType.TIMESTAMP) { + sb.append(" NULL"); + } + + if (mf.isAutoIncreasement()) + sb.append(" IDENTITY(1,1)"); + + if (mf.getColumnType() == ColType.TIMESTAMP) { + if (mf.hasDefaultValue()) { + sb.append(" ").append(getDefaultValue(mf)); + } else { + if (mf.isNotNull()) { + sb.append(" DEFAULT 0"); + } else { + sb.append(" DEFAULT NULL"); + } + } + } else { + if (mf.hasDefaultValue()) + addDefaultValue(sb, mf); + } + } + + if (mf.hasColumnComment()) { + sb.append(" COMMENT '").append(mf.getColumnComment()).append("'"); + } + + sb.append(','); + } + // 创建主键 + List pks = en.getPks(); + if (!pks.isEmpty()) { + sb.append('\n'); + sb.append("PRIMARY KEY ("); + for (MappingField pk : pks) { + sb.append(pk.getColumnNameInSql()).append(','); + } + sb.setCharAt(sb.length() - 1, ')'); + sb.append("\n "); + } + + // 结束表字段设置 + sb.setCharAt(sb.length() - 1, ')'); + // 执行创建语句 + dao.execute(Sqls.create(sb.toString())); + + // 创建索引 + dao.execute(createIndexs(en).toArray(new Sql[0])); + + // 创建关联表 + createRelation(dao, en); + + return true; + } + + protected String createResultSetMetaSql(Entity en) { + return "SELECT * FROM " + en.getViewName() + " LIMIT 1"; + } + + public boolean canCommentWhenAddIndex() { + return true; + } + + protected Sql createRelation(Dao dao, LinkField lf) { + Sql sql = super.createRelation(dao, lf); + if (sql == null) + return null; + StringBuilder sb = new StringBuilder(sql.getSourceSql()); + return Sqls.create(sb.toString()); + } +} diff --git a/src/org/nutz/dao/impl/jdbc/h2/H2JdbcExpert.java b/src/org/nutz/dao/impl/jdbc/h2/H2JdbcExpert.java index a4cf255c84..9d0c306ae2 100644 --- a/src/org/nutz/dao/impl/jdbc/h2/H2JdbcExpert.java +++ b/src/org/nutz/dao/impl/jdbc/h2/H2JdbcExpert.java @@ -45,4 +45,7 @@ public List getIndexNames(Entity en, Connection conn) throws SQLExcep } return names; } + + public void checkDataSource(Connection conn) throws SQLException { + } } diff --git a/src/org/nutz/dao/impl/jdbc/hsqldb/HsqldbJdbcExpert.java b/src/org/nutz/dao/impl/jdbc/hsqldb/HsqldbJdbcExpert.java index 78cb3fe53f..e466d238a3 100644 --- a/src/org/nutz/dao/impl/jdbc/hsqldb/HsqldbJdbcExpert.java +++ b/src/org/nutz/dao/impl/jdbc/hsqldb/HsqldbJdbcExpert.java @@ -100,7 +100,7 @@ public String evalFieldType(MappingField mf) { return "NUMERIC(" + mf.getWidth() + "," + mf.getPrecision() + ")"; } // 用默认精度 - if (mf.getTypeMirror().isDouble()) + if (mf.getMirror().isDouble()) return "NUMERIC(15,10)"; return "FLOAT"; case BINARY: diff --git a/src/org/nutz/dao/impl/jdbc/mysql/MysqlJdbcExpert.java b/src/org/nutz/dao/impl/jdbc/mysql/MysqlJdbcExpert.java index e27930196e..bd1fd34bdd 100644 --- a/src/org/nutz/dao/impl/jdbc/mysql/MysqlJdbcExpert.java +++ b/src/org/nutz/dao/impl/jdbc/mysql/MysqlJdbcExpert.java @@ -27,9 +27,11 @@ public class MysqlJdbcExpert extends AbstractJdbcExpert { - private static final String META_ENGINE = "mysql-engine"; + protected static final String META_ENGINE = "mysql-engine"; - private static final String META_CHARSET = "mysql-charset"; + protected static final String META_CHARSET = "mysql-charset"; + + protected static final String META_INTLEN = "mysql-intlen"; private static final Log log = Logs.get(); @@ -61,19 +63,23 @@ public void formatQuery(Sql sql) { public String evalFieldType(MappingField mf) { if (mf.getCustomDbType() != null) return mf.getCustomDbType(); + int intLen = 4; + if (mf.getEntity().hasMeta(META_INTLEN)) { + intLen = ((Number)mf.getEntity().getMeta(META_INTLEN)).intValue(); + } // Mysql 的精度是按照 bit if (mf.getColumnType() == ColType.INT) { int width = mf.getWidth(); if (width <= 0) { return "INT(32)"; } else if (width <= 2) { - return "TINYINT(" + (width * 4) + ")"; + return "TINYINT(" + (width * intLen) + ")"; } else if (width <= 4) { - return "MEDIUMINT(" + (width * 4) + ")"; + return "MEDIUMINT(" + (width * intLen) + ")"; } else if (width <= 8) { - return "INT(" + (width * 4) + ")"; + return "INT(" + (width * intLen) + ")"; } - return "BIGINT(" + (width * 4) + ")"; + return "BIGINT(" + (width * intLen) + ")"; } if (mf.getColumnType() == ColType.BINARY) { return "MediumBlob"; // 默认用16M的应该可以了吧? @@ -190,7 +196,12 @@ public Pojo fetchPojoId(Entity en, MappingField idField) { @Override public ValueAdaptor getAdaptor(MappingField ef) { if (ColType.MYSQL_JSON == ef.getColumnType()) { - return new MysqlJsonAdaptor(); + ValueAdaptor adaptor = ef.getAdaptor(); + if (adaptor instanceof MysqlJsonAdaptor) { + return adaptor; + } else { + return new MysqlJsonAdaptor(); + } } else { return super.getAdaptor(ef); } diff --git a/src/org/nutz/dao/impl/jdbc/mysql/MysqlJsonAdaptor.java b/src/org/nutz/dao/impl/jdbc/mysql/MysqlJsonAdaptor.java index 99ed5f5b7d..d5513091b7 100644 --- a/src/org/nutz/dao/impl/jdbc/mysql/MysqlJsonAdaptor.java +++ b/src/org/nutz/dao/impl/jdbc/mysql/MysqlJsonAdaptor.java @@ -17,6 +17,8 @@ * 注意,必要的时候需要给 POJO 添加带一个参数的静态工厂方法或者带一个参数的构造函数
* 显示的使用 java.sql.ResultSet 来创建该 POJO,不然会出现无法映射的错误。 *

+ * 数据库中默认使用 {@code JsonFormat.tidy()} 格式来保存JSON类型字段的值。 + *

* *

  * public class Pet {
@@ -59,6 +61,16 @@
  */
 public class MysqlJsonAdaptor implements ValueAdaptor {
 
+    private JsonFormat jsonFormat;
+
+    public MysqlJsonAdaptor() {
+        this.jsonFormat = JsonFormat.tidy();
+    }
+
+    public void setJsonFormat(JsonFormat jsonFormat) {
+        this.jsonFormat = jsonFormat;
+    }
+
     public Object get(ResultSet rs, String colName) throws SQLException {
         return rs.getObject(colName);
     }
@@ -67,7 +79,7 @@ public void set(PreparedStatement stat, Object obj, int index) throws SQLExcepti
         if (null == obj) {
             stat.setNull(index, Types.NULL);
         } else {
-            stat.setObject(index, Json.toJson(obj, JsonFormat.tidy()), Types.VARCHAR);
+            stat.setObject(index, Json.toJson(obj, jsonFormat), Types.VARCHAR);
         }
     }
 }
diff --git a/src/org/nutz/dao/impl/jdbc/mysql/MysqlJsonCompactAdaptor.java b/src/org/nutz/dao/impl/jdbc/mysql/MysqlJsonCompactAdaptor.java
new file mode 100644
index 0000000000..bb1b0aec8e
--- /dev/null
+++ b/src/org/nutz/dao/impl/jdbc/mysql/MysqlJsonCompactAdaptor.java
@@ -0,0 +1,22 @@
+package org.nutz.dao.impl.jdbc.mysql;
+
+import org.nutz.json.JsonFormat;
+
+/**
+ * 数据库中使用 {@code JsonFormat.compact()} 格式来保存JSON类型字段的值。
+ * 

+ * 使用时 {@code ColDefine} 注解的 {@code adaptor} 属性显示声明为 {@code MysqlJsonCompactAdaptor.class} + *

+ *

+ * {@code
+ * @ColDefine(customType = "json", type = ColType.MYSQL_JSON, adaptor = MysqlJsonCompactAdaptor.class)
+ * private Information info;
+ * }
+ * 
+ */ +public class MysqlJsonCompactAdaptor extends MysqlJsonAdaptor { + + public MysqlJsonCompactAdaptor() { + setJsonFormat(JsonFormat.compact()); + } +} diff --git a/src/org/nutz/dao/impl/jdbc/mysql/MysqlJsonTidyAdaptor.java b/src/org/nutz/dao/impl/jdbc/mysql/MysqlJsonTidyAdaptor.java new file mode 100644 index 0000000000..dad47c8ff7 --- /dev/null +++ b/src/org/nutz/dao/impl/jdbc/mysql/MysqlJsonTidyAdaptor.java @@ -0,0 +1,22 @@ +package org.nutz.dao.impl.jdbc.mysql; + +import org.nutz.json.JsonFormat; + +/** + * 数据库中使用 {@code JsonFormat.tidy()} 格式来保存JSON类型字段的值。 + *

+ * 使用时 {@code ColDefine} 注解的 {@code adaptor} 属性显示声明为 {@code MysqlJsonTidyAdaptor.class} + *

+ *

+ * {@code
+ * @ColDefine(customType = "json", type = ColType.MYSQL_JSON, adaptor = MysqlJsonTidyAdaptor.class)
+ * private Information info;
+ * }
+ * 
+ */ +public class MysqlJsonTidyAdaptor extends MysqlJsonAdaptor { + + public MysqlJsonTidyAdaptor() { + setJsonFormat(JsonFormat.tidy()); + } +} diff --git a/src/org/nutz/dao/impl/jdbc/oracle/OracleJdbcExpert.java b/src/org/nutz/dao/impl/jdbc/oracle/OracleJdbcExpert.java index 17f1c761e3..25fb693919 100644 --- a/src/org/nutz/dao/impl/jdbc/oracle/OracleJdbcExpert.java +++ b/src/org/nutz/dao/impl/jdbc/oracle/OracleJdbcExpert.java @@ -4,6 +4,7 @@ import org.nutz.dao.Dao; import org.nutz.dao.Sqls; import org.nutz.dao.entity.Entity; +import org.nutz.dao.entity.LinkField; import org.nutz.dao.entity.MappingField; import org.nutz.dao.entity.PkType; import org.nutz.dao.entity.annotation.ColType; @@ -14,10 +15,12 @@ import org.nutz.dao.jdbc.Jdbcs; import org.nutz.dao.jdbc.ValueAdaptor; import org.nutz.dao.pager.Pager; +import org.nutz.dao.sql.PItem; import org.nutz.dao.sql.Pojo; import org.nutz.dao.sql.Sql; import org.nutz.dao.util.Pojos; import org.nutz.lang.Mirror; +import org.nutz.lang.util.NutMap; import java.sql.*; import java.util.ArrayList; @@ -54,13 +57,14 @@ public class OracleJdbcExpert extends AbstractJdbcExpert { + " END IF;" + " END ${T}_${F}_ST;"; + protected boolean ignoreOneRowPager; public OracleJdbcExpert(JdbcExpertConfigFile conf) { super(conf); } public ValueAdaptor getAdaptor(MappingField ef) { - Mirror mirror = ef.getTypeMirror(); + Mirror mirror = ef.getMirror(); if (mirror.isBoolean()) return new OracleBooleanAdaptor(); if (mirror.isOf(Clob.class)) @@ -163,6 +167,8 @@ public void formatQuery(Pojo pojo) { Pager pager = pojo.getContext().getPager(); // 需要进行分页 if (null != pager && pager.getPageNumber() > 0) { + if (ignoreOneRowPager && pager.getPageNumber() == 1 && pager.getPageSize() == 1) + return; pojo.insertFirst(Pojos.Items.wrap("SELECT * FROM (SELECT T.*, ROWNUM RN FROM (")); pojo.append(Pojos.Items.wrapf(") T WHERE ROWNUM <= %d) WHERE RN > %d", pager.getOffset() + pager.getPageSize(), @@ -175,6 +181,8 @@ public void formatQuery(Sql sql) { Pager pager = sql.getContext().getPager(); // 需要进行分页 if (null != pager && pager.getPageNumber() > 0) { + if (ignoreOneRowPager && pager.getPageNumber() == 1 && pager.getPageSize() == 1) + return; String pre = "SELECT * FROM (SELECT T.*, ROWNUM RN FROM ("; String last = String.format(") T WHERE ROWNUM <= %d) WHERE RN > %d", pager.getOffset() + pager.getPageSize(), @@ -212,7 +220,7 @@ public String evalFieldType(MappingField mf) { return "NUMBER(" + mf.getWidth() + "," + mf.getPrecision() + ")"; } // 用默认精度 - if (mf.getTypeMirror().isDouble()) + if (mf.getMirror().isDouble()) return "NUMBER(15,10)"; return "NUMBER"; case TIME: @@ -262,7 +270,7 @@ public boolean supportTimestampDefault() { return false; } - public String wrapKeywork(String columnName, boolean force) { + public String wrapKeyword(String columnName, boolean force) { if (force || keywords.contains(columnName.toUpperCase())) return "\"" + columnName + "\""; return null; @@ -280,4 +288,23 @@ public List getIndexNames(Entity en, Connection conn) throws SQLExcep } return names; } + + public void setupProperties(NutMap conf) { + super.setupProperties(conf); + this.ignoreOneRowPager = conf.getBoolean("nutz.dao.jdbc.oracle.ignoreOneRowPager", false); + } + + @Override + public PItem formatLeftJoinLink(Object obj, LinkField lnk, Entity en) { + Entity lnkEntity = lnk.getLinkedEntity(); + String linkName = safeTableName(lnk.getName()); + String LJ = String.format("LEFT JOIN %s %s ON %s.%s = %s.%s", + lnkEntity.getTableName(), + linkName, + en.getTableName(), + lnk.getHostField().getColumnNameInSql(), + linkName, + lnk.getLinkedField().getColumnNameInSql()); + return Pojos.Items.wrap(LJ); + } } diff --git a/src/org/nutz/dao/impl/jdbc/psql/PsqlJdbcExpert.java b/src/org/nutz/dao/impl/jdbc/psql/PsqlJdbcExpert.java index 48b29b9b3d..cb0706d239 100644 --- a/src/org/nutz/dao/impl/jdbc/psql/PsqlJdbcExpert.java +++ b/src/org/nutz/dao/impl/jdbc/psql/PsqlJdbcExpert.java @@ -1,6 +1,12 @@ package org.nutz.dao.impl.jdbc.psql; import java.sql.Blob; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.nutz.dao.DB; @@ -11,7 +17,7 @@ import org.nutz.dao.entity.PkType; import org.nutz.dao.entity.annotation.ColType; import org.nutz.dao.impl.jdbc.AbstractJdbcExpert; -import org.nutz.dao.impl.jdbc.BlobValueAdaptor2; +import org.nutz.dao.impl.jdbc.BlobValueAdaptor3; import org.nutz.dao.jdbc.JdbcExpertConfigFile; import org.nutz.dao.jdbc.Jdbcs; import org.nutz.dao.jdbc.ValueAdaptor; @@ -20,9 +26,13 @@ import org.nutz.dao.sql.Sql; import org.nutz.dao.util.Pojos; import org.nutz.lang.Lang; +import org.nutz.log.Log; +import org.nutz.log.Logs; public class PsqlJdbcExpert extends AbstractJdbcExpert { + private static final Log log = Logs.get(); + public PsqlJdbcExpert(JdbcExpertConfigFile conf) { super(conf); } @@ -128,7 +138,7 @@ public String evalFieldType(MappingField mf) { return "NUMERIC(" + mf.getWidth() + "," + mf.getPrecision() + ")"; } // 用默认精度 - if (mf.getTypeMirror().isDouble()) + if (mf.getMirror().isDouble()) return "NUMERIC(15,10)"; return "NUMERIC"; @@ -156,20 +166,79 @@ protected String createResultSetMetaSql(Entity en) { @Override public ValueAdaptor getAdaptor(MappingField ef) { - if (ef.getTypeMirror().isOf(Blob.class)) { - return new BlobValueAdaptor2(Jdbcs.getFilePool()); + if (ef.getMirror().isOf(Blob.class)) { + return new BlobValueAdaptor3(Jdbcs.getFilePool()); } else if (ColType.PSQL_JSON == ef.getColumnType()) { - return new PsqlJsonAdaptor(); + ValueAdaptor adaptor = ef.getAdaptor(); + if (adaptor instanceof PsqlJsonAdaptor) { + return adaptor; + } else { + return new PsqlJsonAdaptor(); + } } else if (ColType.PSQL_ARRAY == ef.getColumnType()) { return new PsqlArrayAdaptor(ef.getCustomDbType()); } else { return super.getAdaptor(ef); } } - - public String wrapKeywork(String columnName, boolean force) { + + public String wrapKeyword(String columnName, boolean force) { if (force || keywords.contains(columnName.toUpperCase())) return "\"" + columnName + "\""; return null; } + + @Override + public void checkDataSource(Connection conn) throws SQLException { + if (log.isDebugEnabled()) { + String sql = "SELECT * FROM information_schema.character_sets"; + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery(sql); + if (rs.next()) { + for (String name : Arrays.asList("character_set_catalog", + "character_set_schema", + "character_set_name", + "character_repertoire", + "form_of_use", + "default_collate_catalog", + "default_collate_schema", + "default_collate_name")) { + log.debugf("Postgresql : %s=%s", name, rs.getString(name)); + } + } + rs.close(); + // 打印当前数据库名称 + rs = stmt.executeQuery("SELECT CURRENT_DATABASE()"); + if (rs.next()) { + log.debug("Postgresql : database=" + rs.getString(1)); + } + rs.close(); + // 打印当前连接用户名 + rs = stmt.executeQuery("SELECT CURRENT_USER"); + if (rs.next()) { + log.debug("Postgresql : user=" + rs.getString(1)); + } + rs.close(); + stmt.close(); + } + } + + /** + * @author enzozhong( haowen.zhong@foxmail.com ) + */ + @Override + public List getIndexNames(Entity en, Connection conn) throws SQLException { + + String tableName = en.getTableName(); + String sql = "SELECT * FROM pg_indexes WHERE tablename='" + tableName + "'"; + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery(sql); + + ArrayList indexNames = new ArrayList(17); + while (rs.next()) { + indexNames.add(rs.getString("indexname")); + } + + return indexNames; + } } diff --git a/src/org/nutz/dao/impl/jdbc/psql/PsqlJsonAdaptor.java b/src/org/nutz/dao/impl/jdbc/psql/PsqlJsonAdaptor.java index 0ff6750611..63ac54f5ef 100644 --- a/src/org/nutz/dao/impl/jdbc/psql/PsqlJsonAdaptor.java +++ b/src/org/nutz/dao/impl/jdbc/psql/PsqlJsonAdaptor.java @@ -17,6 +17,9 @@ * 注意,必要的时候需要给 POJO 添加带一个参数的静态工厂方法或者带一个参数的构造函数
* 显示的使用 java.sql.ResultSet 来创建该 POJO,不然会出现无法映射的错误。 *

+ * 数据库中默认使用 {@code JsonFormat.tidy()} 格式来保存JSON类型字段的值。 + *

+ * *

  * public class Pet {
  *
@@ -58,6 +61,16 @@
  */
 public class PsqlJsonAdaptor implements ValueAdaptor {
 
+    private JsonFormat jsonFormat;
+
+    public PsqlJsonAdaptor() {
+        this.jsonFormat = JsonFormat.tidy();
+    }
+
+    public void setJsonFormat(JsonFormat jsonFormat) {
+        this.jsonFormat = jsonFormat;
+    }
+
     public Object get(ResultSet rs, String colName) throws SQLException {
         return rs.getObject(colName);
     }
@@ -66,7 +79,7 @@ public void set(PreparedStatement stat, Object obj, int index) throws SQLExcepti
         if (null == obj) {
             stat.setNull(index, Types.NULL);
         } else {
-            stat.setObject(index, Json.toJson(obj, JsonFormat.tidy()), Types.OTHER);
+            stat.setObject(index, Json.toJson(obj, jsonFormat), Types.OTHER);
         }
     }
 }
diff --git a/src/org/nutz/dao/impl/jdbc/psql/PsqlJsonCompactAdaptor.java b/src/org/nutz/dao/impl/jdbc/psql/PsqlJsonCompactAdaptor.java
new file mode 100644
index 0000000000..a4ce03d614
--- /dev/null
+++ b/src/org/nutz/dao/impl/jdbc/psql/PsqlJsonCompactAdaptor.java
@@ -0,0 +1,22 @@
+package org.nutz.dao.impl.jdbc.psql;
+
+import org.nutz.json.JsonFormat;
+
+/**
+ * 数据库中使用 {@code JsonFormat.compact()} 格式来保存JSON类型字段的值。
+ * 

+ * 使用时 {@code ColDefine} 注解的 {@code adaptor} 属性显示声明为 {@code PsqlJsonCompactAdaptor.class} + *

+ *

+ * {@code
+ * @ColDefine(customType = "json", type = ColType.PSQL_JSON, adaptor = PsqlJsonCompactAdaptor.class)
+ * private Information info;
+ * }
+ * 
+ */ +public class PsqlJsonCompactAdaptor extends PsqlJsonAdaptor { + + public PsqlJsonCompactAdaptor() { + setJsonFormat(JsonFormat.compact()); + } +} diff --git a/src/org/nutz/dao/impl/jdbc/psql/PsqlJsonTidyAdaptor.java b/src/org/nutz/dao/impl/jdbc/psql/PsqlJsonTidyAdaptor.java new file mode 100644 index 0000000000..83004fc06d --- /dev/null +++ b/src/org/nutz/dao/impl/jdbc/psql/PsqlJsonTidyAdaptor.java @@ -0,0 +1,22 @@ +package org.nutz.dao.impl.jdbc.psql; + +import org.nutz.json.JsonFormat; + +/** + * 数据库中使用 {@code JsonFormat.tidy()} 格式来保存JSON类型字段的值。 + *

+ * 使用时 {@code ColDefine} 注解的 {@code adaptor} 属性显示声明为 {@code PsqlJsonTidyAdaptor.class} + *

+ *

+ * {@code
+ * @ColDefine(customType = "json", type = ColType.PSQL_JSON, adaptor = PsqlJsonTidyAdaptor.class)
+ * private Information info;
+ * }
+ * 
+ */ +public class PsqlJsonTidyAdaptor extends PsqlJsonAdaptor { + + public PsqlJsonTidyAdaptor() { + setJsonFormat(JsonFormat.tidy()); + } +} diff --git a/src/org/nutz/dao/impl/jdbc/sqlserver2005/Sqlserver2005JdbcExpert.java b/src/org/nutz/dao/impl/jdbc/sqlserver2005/Sqlserver2005JdbcExpert.java index bed1e8a418..565691d2e2 100644 --- a/src/org/nutz/dao/impl/jdbc/sqlserver2005/Sqlserver2005JdbcExpert.java +++ b/src/org/nutz/dao/impl/jdbc/sqlserver2005/Sqlserver2005JdbcExpert.java @@ -7,6 +7,7 @@ import java.util.ArrayList; import java.util.List; +import org.nutz.conf.NutConf; import org.nutz.dao.DB; import org.nutz.dao.Dao; import org.nutz.dao.Sqls; @@ -140,9 +141,13 @@ public String evalFieldType(MappingField mf) { return "decimal(" + mf.getWidth() + "," + mf.getPrecision() + ")"; } // 用默认精度 - if (mf.getTypeMirror().isDouble()) + if (mf.getMirror().isDouble()) return "decimal(15,10)"; return "float"; + case VARCHAR: + if (NutConf.SQLSERVER_USE_NVARCHAR) + return "NVARCHAR(" + mf.getWidth() + ")"; + return "VARCHAR(" + mf.getWidth() + ")"; case BINARY: return "varbinary(max)"; //case TEXT : @@ -150,6 +155,7 @@ public String evalFieldType(MappingField mf) { default : break; } + return super.evalFieldType(mf); } @@ -204,7 +210,7 @@ public boolean addColumnNeedColumn() { return false; } - public String wrapKeywork(String columnName, boolean force) { + public String wrapKeyword(String columnName, boolean force) { if (force || keywords.contains(columnName.toUpperCase())) return "[" + columnName + "]"; return null; diff --git a/src/org/nutz/dao/impl/jdbc/tdengine/TDengineJdbcExpert.java b/src/org/nutz/dao/impl/jdbc/tdengine/TDengineJdbcExpert.java new file mode 100644 index 0000000000..57f78d2e2e --- /dev/null +++ b/src/org/nutz/dao/impl/jdbc/tdengine/TDengineJdbcExpert.java @@ -0,0 +1,102 @@ +package org.nutz.dao.impl.jdbc.tdengine; + +import org.nutz.dao.DB; +import org.nutz.dao.Dao; +import org.nutz.dao.Sqls; +import org.nutz.dao.entity.Entity; +import org.nutz.dao.entity.MappingField; +import org.nutz.dao.impl.jdbc.AbstractJdbcExpert; +import org.nutz.dao.jdbc.JdbcExpertConfigFile; +import org.nutz.dao.jdbc.ValueAdaptor; +import org.nutz.dao.pager.Pager; +import org.nutz.dao.sql.Pojo; +import org.nutz.dao.sql.Sql; +import org.nutz.dao.util.Pojos; +import org.nutz.log.Log; +import org.nutz.log.Logs; + +public class TDengineJdbcExpert extends AbstractJdbcExpert { + private static final Log log = Logs.get(); + + public TDengineJdbcExpert(JdbcExpertConfigFile conf) { + super(conf); + } + + public String getDatabaseType() { + return DB.TDENGINE.name(); + } + + public ValueAdaptor getAdaptor(MappingField ef) { + return super.getAdaptor(ef); + } + + public String evalFieldType(MappingField mf) { + if (mf.getCustomDbType() != null) + return mf.getCustomDbType(); + int intLen = 4; + int width = mf.getWidth(); + switch (mf.getColumnType()) { + case INT: + if (width <= 0) { + return "INT(32)"; + } else if (width <= 2) { + return "TINYINT(" + (width * intLen) + ")"; + } else if (width <= 4) { + return "SMALLINT(" + (width * intLen) + ")"; + } else if (width <= 8) { + return "INT(" + (width * intLen) + ")"; + } + return "BIGINT(" + (width * intLen) + ")"; + case FLOAT: + return "FLOAT"; + case DOUBLE: + return "DOUBLE"; + case BOOLEAN: + return "BOOL"; + case BINARY: + return "BINARY(" + width + ")"; + case CHAR: + case VARCHAR: + return "NCHAR(" + width + ")"; + default: + break; + } + // 其它的参照默认字段规则 ... + return super.evalFieldType(mf); + } + + public boolean createEntity(Dao dao, Entity en) { + StringBuilder sb = new StringBuilder("CREATE TABLE " + en.getTableName() + "("); + // 创建字段 + for (MappingField mf : en.getMappingFields()) { + if (mf.isReadonly()) + continue; + sb.append('\n').append(mf.getColumnNameInSql()); + sb.append(' ').append(evalFieldType(mf)); + sb.append(','); + } + // 结束表字段设置 + sb.setCharAt(sb.length() - 1, ')'); + // 执行创建语句 + dao.execute(Sqls.create(sb.toString())); + return true; + } + + + public void formatQuery(Pojo pojo) { + Pager pager = pojo.getContext().getPager(); + // 需要进行分页 + if (null != pager && pager.getPageNumber() > 0) + pojo.append(Pojos.Items.wrapf(" LIMIT %d, %d", pager.getOffset(), pager.getPageSize())); + } + + public void formatQuery(Sql sql) { + Pager pager = sql.getContext().getPager(); + // 需要进行分页 + if (null != pager && pager.getPageNumber() > 0) + sql.setSourceSql(sql.getSourceSql() + + String.format(" LIMIT %d, %d", + pager.getOffset(), + pager.getPageSize())); + } +} diff --git a/src/org/nutz/dao/impl/jdbc/xugu/XuguJdbcExpert.java b/src/org/nutz/dao/impl/jdbc/xugu/XuguJdbcExpert.java new file mode 100644 index 0000000000..0b1f029842 --- /dev/null +++ b/src/org/nutz/dao/impl/jdbc/xugu/XuguJdbcExpert.java @@ -0,0 +1,11 @@ +package org.nutz.dao.impl.jdbc.xugu; + +import org.nutz.dao.impl.jdbc.oracle.OracleJdbcExpert; +import org.nutz.dao.jdbc.JdbcExpertConfigFile; + + +public class XuguJdbcExpert extends OracleJdbcExpert { + public XuguJdbcExpert(JdbcExpertConfigFile conf) { + super(conf); + } +} diff --git a/src/org/nutz/dao/impl/jdbc/yashan/YashanBooleanAdaptor.java b/src/org/nutz/dao/impl/jdbc/yashan/YashanBooleanAdaptor.java new file mode 100644 index 0000000000..74746d9ff9 --- /dev/null +++ b/src/org/nutz/dao/impl/jdbc/yashan/YashanBooleanAdaptor.java @@ -0,0 +1,38 @@ +package org.nutz.dao.impl.jdbc.yashan; + +import org.nutz.dao.jdbc.ValueAdaptor; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; + +/** + * 对 Oracle,Types.BOOLEAN 对于 setNull 是不工作的 其他的数据库都没有这个问题,
+ * 所以,只好把类型设成 INTEGER了 + */ +public class YashanBooleanAdaptor implements ValueAdaptor { + + public Object get(ResultSet rs, String colName) throws SQLException { + boolean re = rs.getBoolean(colName); + return rs.wasNull() ? null : re; + } + + public void set(PreparedStatement stat, Object obj, int i) throws SQLException { + if (null == obj) { + stat.setNull(i, Types.BOOLEAN); + } else { + boolean v; + if (obj instanceof Boolean) + v = (Boolean) obj; + else if (obj instanceof Number) + v = ((Number) obj).intValue() > 0; + else if (obj instanceof Character) + v = Character.toUpperCase((Character) obj) == 'T'; + else + v = Boolean.valueOf(obj.toString()); + stat.setBoolean(i, v); + } + } + +} diff --git a/src/org/nutz/dao/impl/jdbc/yashan/YashanJdbcExpert.java b/src/org/nutz/dao/impl/jdbc/yashan/YashanJdbcExpert.java new file mode 100644 index 0000000000..32918cc43e --- /dev/null +++ b/src/org/nutz/dao/impl/jdbc/yashan/YashanJdbcExpert.java @@ -0,0 +1,319 @@ +package org.nutz.dao.impl.jdbc.yashan; + +import org.nutz.dao.DB; +import org.nutz.dao.Dao; +import org.nutz.dao.Sqls; +import org.nutz.dao.entity.Entity; +import org.nutz.dao.entity.LinkField; +import org.nutz.dao.entity.MappingField; +import org.nutz.dao.entity.PkType; +import org.nutz.dao.entity.annotation.ColType; +import org.nutz.dao.impl.jdbc.AbstractJdbcExpert; +import org.nutz.dao.impl.jdbc.BlobValueAdaptor2; +import org.nutz.dao.impl.jdbc.ClobValueAdapter2; +import org.nutz.dao.jdbc.JdbcExpertConfigFile; +import org.nutz.dao.jdbc.Jdbcs; +import org.nutz.dao.jdbc.ValueAdaptor; +import org.nutz.dao.pager.Pager; +import org.nutz.dao.sql.PItem; +import org.nutz.dao.sql.Pojo; +import org.nutz.dao.sql.Sql; +import org.nutz.dao.util.Pojos; +import org.nutz.lang.Mirror; +import org.nutz.lang.util.NutMap; + +import java.sql.*; +import java.util.ArrayList; +import java.util.List; + +public class YashanJdbcExpert extends AbstractJdbcExpert { + + //指定崖山表空间的TableMeta' key + private static final String META_TABLESPACE = "yashan-tablespace"; + + //崖山创建表时指定表空间的默认sql + private static String CTS = "tablespace %s\n" + + " pctfree 10\n" + + " initrans 1\n" + + " maxtrans 255\n" + + " storage\n" + + " (\n" + + " initial 64K\n" + + " minextents 1\n" + + " maxextents unlimited\n" + + " )"; + + private static String CSEQ = "CREATE SEQUENCE ${T}_${F}_SEQ MINVALUE 1" + + " MAXVALUE 999999999999 INCREMENT BY 1 START" + + " WITH 1 CACHE 20 NOORDER NOCYCLE"; + private static String DSEQ = "DROP SEQUENCE ${T}_${F}_SEQ"; + + private static String CTRI = "create or replace trigger ${T}_${F}_ST" + + " BEFORE INSERT ON ${T}" + + " FOR EACH ROW" + + " BEGIN " + + " IF :new.${F} IS NULL THEN" + + " SELECT ${T}_${F}_seq.nextval into :new.${F} FROM dual;" + + " END IF;" + + " END ${T}_${F}_ST;"; + + protected boolean ignoreOneRowPager; + + public YashanJdbcExpert(JdbcExpertConfigFile conf) { + super(conf); + } + + public ValueAdaptor getAdaptor(MappingField ef) { + Mirror mirror = ef.getMirror(); + if (mirror.isBoolean()) + return new YashanBooleanAdaptor(); + if (mirror.isOf(Clob.class)) + return new ClobValueAdapter2(Jdbcs.getFilePool()); + if (mirror.isOf(Blob.class)) + return new BlobValueAdaptor2(Jdbcs.getFilePool()); + return super.getAdaptor(ef); + } + + public boolean createEntity(Dao dao, Entity en) { + StringBuilder sb = new StringBuilder("CREATE TABLE " + en.getTableName() + "("); + // 创建字段 + for (MappingField mf : en.getMappingFields()) { + if (mf.isReadonly()) + continue; + sb.append('\n').append(mf.getColumnNameInSql()); + sb.append(' ').append(evalFieldType(mf)); + // 非主键的 @Name,应该加入唯一性约束 + if (mf.isName() && en.getPkType() != PkType.NAME) { + sb.append(" NOT NULL UNIQUE"); + } + // 普通字段 + else { + if (mf.isPk() && en.getPks().size() == 1) + sb.append(" primary key "); + if (mf.isNotNull()) + sb.append(" NOT NULL"); + if (mf.hasDefaultValue() && mf.getColumnType() != ColType.BOOLEAN) + addDefaultValue(sb, mf); + if (mf.isUnsigned() && mf.getColumnType() != ColType.BOOLEAN) // 有点暴力 + sb.append(" Check ( ").append(mf.getColumnNameInSql()).append(" >= 0)"); + } + sb.append(','); + } + + // 结束表字段设置 + sb.setCharAt(sb.length() - 1, ')'); + + //指定表空间 + if (en.hasMeta(META_TABLESPACE)) { + sb.append(String.format(CTS, en.getMeta(META_TABLESPACE))); + } + + List sqls = new ArrayList(); + sqls.add(Sqls.create(sb.toString())); + + // 创建复合主键 + List pks = en.getPks(); + if (pks.size() > 1) { + StringBuilder pkNames = new StringBuilder(); + for (MappingField pk : pks) { + pkNames.append(pk.getColumnName()).append(','); + } + pkNames.setLength(pkNames.length() - 1); + + String pkNames2 = makePksName(en); + + String sql = String.format("alter table %s add constraint primary_key_%s primary key (%s)", + en.getTableName(), + pkNames2, + pkNames); + sqls.add(Sqls.create(sql)); + } + // // 处理非主键unique + // for (MappingField mf : en.getMappingFields()) { + // if(!mf.isPk()) + // continue; + // String sql = + // gSQL("alter table ${T} add constraint unique_key_${F} unique (${F});", + // en.getTableName(),mf.getColumnName()); + // sqls.add(Sqls.create(sql)); + // } + // 处理AutoIncreasement + for (MappingField mf : en.getMappingFields()) { + if (!mf.isAutoIncreasement()) + continue; + // 序列 + sqls.add(Sqls.create(gSQL(CSEQ, en.getTableName(), mf.getColumnName()))); + // 触发器 + sqls.add(Sqls.create(gSQL(CTRI, en.getTableName(), mf.getColumnName()))); + } + + // 创建索引 + sqls.addAll(createIndexs(en)); + + // TODO 详细处理Clob + // TODO 详细处理Blob + + // 执行创建语句 + dao.execute(sqls.toArray(new Sql[sqls.size()])); + // 创建关联表 + createRelation(dao, en); + // 添加注释(表注释与字段注释) + addComment(dao, en); + + return true; + } + + public void formatQuery(Pojo pojo) { + Pager pager = pojo.getContext().getPager(); + // 需要进行分页 + if (null != pager && pager.getPageNumber() > 0) { + if (ignoreOneRowPager && pager.getPageNumber() == 1 && pager.getPageSize() == 1) + return; + pojo.insertFirst(Pojos.Items.wrap("SELECT * FROM (SELECT T.*, ROWNUM RN FROM (")); + pojo.append(Pojos.Items.wrapf(") T WHERE ROWNUM <= %d) WHERE RN > %d", + pager.getOffset() + pager.getPageSize(), + pager.getOffset())); + } + } + + @Override + public void formatQuery(Sql sql) { + Pager pager = sql.getContext().getPager(); + // 需要进行分页 + if (null != pager && pager.getPageNumber() > 0) { + if (ignoreOneRowPager && pager.getPageNumber() == 1 && pager.getPageSize() == 1) + return; + String pre = "SELECT * FROM (SELECT T.*, ROWNUM RN FROM ("; + String last = String.format(") T WHERE ROWNUM <= %d) WHERE RN > %d", + pager.getOffset() + pager.getPageSize(), + pager.getOffset()); + sql.setSourceSql(pre + sql.getSourceSql() + last); + } + } + + public String getDatabaseType() { + return DB.YASHAN.name(); + } + + public String evalFieldType(MappingField mf) { + if (mf.getCustomDbType() != null) + return mf.getCustomDbType(); + int intLen = 4; + switch (mf.getColumnType()) { + case BOOLEAN: + if (mf.hasDefaultValue()) + return "boolean DEFAULT " + getDefaultValue(mf) + " check (" + mf.getColumnNameInSql() + " in(true,false))"; + return "boolean check (" + mf.getColumnNameInSql() + " in(true,false))"; + case TEXT: + return "CLOB"; + case VARCHAR: + // 崖山中 VARCHAR2的宽度单位是字节,加上char才是字符 + return "VARCHAR2(" + mf.getWidth() + " char)"; + + case INT: + int width = mf.getWidth(); + if (width <= 0) { + return "INT"; + } else if (width <= 2) { + return "TINYINT"; + } else if (width <= 4) { + return "SMALLINT"; + } else if (width <= 8) { + return "INT"; + } + return "BIGINT"; + + case FLOAT: + // 用户自定义了精度 + if (mf.getWidth() > 0 && mf.getPrecision() > 0) { + return "NUMBER(" + mf.getWidth() + "," + mf.getPrecision() + ")"; + } + // 用默认精度 + if (mf.getMirror().isDouble()) + return "NUMBER(15,10)"; + return "NUMBER"; + case TIME: + case DATETIME: + case DATE: + return "DATE"; + default: + return super.evalFieldType(mf); + } + } + + @Override + protected String createResultSetMetaSql(Entity en) { + return "select * from " + en.getViewName() + " where rownum <= 1"; + } + + @Override + public boolean dropEntity(Dao dao, Entity en) { + if (super.dropEntity(dao, en)) { + if (en.getPks().isEmpty()) + return true; + List sqls = new ArrayList(); + for (MappingField pk : en.getPks()) { + if (pk.isAutoIncreasement()) { + String sql = gSQL(DSEQ, en.getTableName(), pk.getColumnName()); + sqls.add(Sqls.create(sql)); + } + } + try { + dao.execute(sqls.toArray(new Sql[sqls.size()])); + return true; + } catch (Exception e) { + } + } + return false; + } + + public boolean isSupportAutoIncrement() { + return false; + } + + public boolean addColumnNeedColumn() { + return false; + } + + public boolean supportTimestampDefault() { + return false; + } + + public String wrapKeyword(String columnName, boolean force) { + if (force || keywords.contains(columnName.toUpperCase())) + return "\"" + columnName + "\""; + return null; + } + + // https://docs.oracle.com/cd/B12037_01/server.101/b10755/statviews_1061.htm + public List getIndexNames(Entity en, Connection conn) throws SQLException { + List names = new ArrayList(); + String showIndexs = "SELECT * FROM user_indexes WHERE table_name='" + en.getTableName() + "'"; + PreparedStatement ppstat = conn.prepareStatement(showIndexs); + ResultSet rest = ppstat.executeQuery(); + while (rest.next()) { + String index = rest.getString(2); + names.add(index); + } + return names; + } + + public void setupProperties(NutMap conf) { + super.setupProperties(conf); + this.ignoreOneRowPager = conf.getBoolean("nutz.dao.jdbc.yashan.ignoreOneRowPager", false); + } + + @Override + public PItem formatLeftJoinLink(Object obj, LinkField lnk, Entity en) { + Entity lnkEntity = lnk.getLinkedEntity(); + String linkName = safeTableName(lnk.getName()); + String LJ = String.format("LEFT JOIN %s %s ON %s.%s = %s.%s", + lnkEntity.getTableName(), + linkName, + en.getTableName(), + lnk.getHostField().getColumnNameInSql(), + linkName, + lnk.getLinkedField().getColumnNameInSql()); + return Pojos.Items.wrap(LJ); + } +} diff --git a/src/org/nutz/dao/impl/sql/NutPojoMaker.java b/src/org/nutz/dao/impl/sql/NutPojoMaker.java index 03845373ff..53345ed47e 100644 --- a/src/org/nutz/dao/impl/sql/NutPojoMaker.java +++ b/src/org/nutz/dao/impl/sql/NutPojoMaker.java @@ -1,5 +1,13 @@ package org.nutz.dao.impl.sql; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + import org.nutz.dao.DaoException; import org.nutz.dao.FieldMatcher; import org.nutz.dao.entity.Entity; @@ -8,21 +16,22 @@ import org.nutz.dao.entity.MappingField; import org.nutz.dao.impl.sql.pojo.NoParamsPItem; import org.nutz.dao.jdbc.JdbcExpert; +import org.nutz.dao.sql.PItem; import org.nutz.dao.sql.Pojo; import org.nutz.dao.sql.PojoCallback; import org.nutz.dao.sql.PojoMaker; import org.nutz.dao.sql.SqlType; +import org.nutz.dao.util.Daos; import org.nutz.dao.util.Pojos; -import org.nutz.lang.*; - -import java.sql.Connection; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.Arrays; -import java.util.List; -import java.util.Map; +import org.nutz.lang.ContinueLoop; +import org.nutz.lang.Each; +import org.nutz.lang.ExitLoop; +import org.nutz.lang.Lang; +import org.nutz.lang.LoopException; +/** + * Nut Pojo制造商。 + */ public class NutPojoMaker implements PojoMaker { private JdbcExpert expert; @@ -31,10 +40,12 @@ public NutPojoMaker(JdbcExpert expert) { this.expert = expert; } + @Override public Pojo makePojo(SqlType type) { return expert.createPojo(type); } + @Override public Pojo makeInsert(final Entity en) { Pojo pojo = Pojos.pojo(expert, en, SqlType.INSERT); pojo.setEntity(en); @@ -53,6 +64,7 @@ public Pojo makeInsert(final Entity en) { return pojo; } + @Override public Pojo makeUpdate(Entity en, Object refer) { Pojo pojo = Pojos.pojo(expert, en, SqlType.UPDATE); pojo.setEntity(en); @@ -61,6 +73,7 @@ public Pojo makeUpdate(Entity en, Object refer) { return pojo; } + @Override public Pojo makeQuery(Entity en) { Pojo pojo = Pojos.pojo(expert, en, SqlType.SELECT); pojo.setEntity(en); @@ -70,10 +83,12 @@ public Pojo makeQuery(Entity en) { return pojo; } + @Override public Pojo makeQuery(String tableName) { return makeQuery(tableName, "*"); } + @Override public Pojo makeQuery(String tableName, String fields) { String[] ss = tableName.split(":"); // String idFieldName = ss.length > 1 ? ss[1] : "*";//按id字段来统计,比较快 @@ -85,6 +100,7 @@ public Pojo makeQuery(String tableName, String fields) { return pojo; } + @Override public Pojo makeDelete(Entity en) { Pojo pojo = Pojos.pojo(expert, en, SqlType.DELETE); pojo.setEntity(en); @@ -93,6 +109,7 @@ public Pojo makeDelete(Entity en) { return pojo; } + @Override public Pojo makeDelete(String tableName) { Pojo pojo = makePojo(SqlType.DELETE); pojo.append(Pojos.Items.wrap("FROM")); @@ -100,6 +117,7 @@ public Pojo makeDelete(String tableName) { return pojo; } + @Override public Pojo makeFunc(String tableName, String funcName, String colName) { Pojo pojo = makePojo(SqlType.SELECT); pojo.append(Pojos.Items.wrapf("%s(%s) FROM %s", funcName, colName, tableName)); @@ -108,6 +126,7 @@ public Pojo makeFunc(String tableName, String funcName, String colName) { static class GeneratedKeys implements PojoCallback { + @Override public Object invoke(Connection conn, ResultSet rs, final Pojo pojo, Statement stmt) throws SQLException { final ResultSet _rs = stmt.getGeneratedKeys(); @@ -116,11 +135,13 @@ public Object invoke(Connection conn, ResultSet rs, final Pojo pojo, Statement s obj = Arrays.asList(obj); } Lang.each(obj, new Each() { + @Override public void invoke(int index, Object ele, int length) throws ExitLoop, ContinueLoop, LoopException { try { - if (!_rs.next()) + if (!_rs.next()) { throw new ExitLoop(); + } Object key = _rs.getObject(1); pojo.getEntity().getIdField().setValue(ele, key); } @@ -133,48 +154,69 @@ public void invoke(int index, Object ele, int length) } } + /** + * 按联接进行查询 + * @param en + * @param regex + * @return + */ @Override public Pojo makeQueryByJoin(final Entity en, String regex) { final Pojo pojo = Pojos.pojo(expert, en, SqlType.SELECT); pojo.setEntity(en); - pojo.append(new QueryJoinFeilds(en, true)); - final int[] index = new int[1]; + pojo.append(new QueryJoinFeilds(en, true, en.getTableName())); en.visitOne(null, regex, new LinkVisitor() { + @Override public void visit(Object obj, LinkField lnk) { pojo.append(Pojos.Items.wrap(",")); - pojo.append(new QueryJoinFeilds(lnk.getLinkedEntity(), false)); - index[0]++; + pojo.append(new QueryJoinFeilds(lnk.getLinkedEntity(), false, lnk.getName())); } }); pojo.append(Pojos.Items.wrap("FROM")); pojo.append(Pojos.Items.entityViewName()); - index[0] = 0; en.visitOne(null, regex, new LinkVisitor() { + @Override public void visit(Object obj, LinkField lnk) { - Entity lnkEntity = lnk.getLinkedEntity(); - String LJ = String.format("LEFT JOIN %s ON %s.%s = %s.%s", - lnkEntity.getTableName(), - en.getTableName(), - lnk.getHostField().getColumnNameInSql(), - lnkEntity.getTableName(), - lnk.getLinkedField().getColumnNameInSql()); - pojo.append(Pojos.Items.wrap(LJ)); - index[0]++; + PItem item = expert.formatLeftJoinLink(obj, lnk, en); + if (item != null) + pojo.append(item); } }); return pojo; } + @Override + public Pojo makeCountByJoin(final Entity en, String regex) { + final Pojo pojo = Pojos.pojo(expert, en, SqlType.SELECT); + pojo.setEntity(en); + pojo.append(Pojos.Items.wrap("count(1)")); + pojo.append(Pojos.Items.wrap("FROM")); + pojo.append(Pojos.Items.entityViewName()); + en.visitOne(null, regex, new LinkVisitor() { + @Override + public void visit(Object obj, LinkField lnk) { + PItem item = expert.formatLeftJoinLink(obj, lnk, en); + if (item != null) + pojo.append(item); + } + }); + return pojo; + } + protected static class QueryJoinFeilds extends NoParamsPItem { + private static final long serialVersionUID = 1L; protected Entity en; protected boolean main; + protected String tableName; - public QueryJoinFeilds(Entity en, boolean main) { + public QueryJoinFeilds(Entity en, boolean main, String tableName) { this.en = en; this.main = main; + this.tableName = tableName; } + @Override public void joinSql(Entity en, StringBuilder sb) { en = this.en; FieldMatcher fm = getFieldMatcher(); @@ -184,21 +226,36 @@ public void joinSql(Entity en, StringBuilder sb) { for (MappingField ef : efs) { if (fm == null || fm.match(ef.getName())) { - sb.append(en.getTableName()) + sb.append(tableName) .append(".") .append(ef.getColumnNameInSql()) .append(" as "); - if (!main) - sb.append(en.getTableName()).append("_z_"); + if (!main) { + sb.append(tableName).append("_z_"); + } sb.append(ef.getColumnNameInSql()).append(','); } } - if (sb.length() == old) + if (sb.length() == old) { throw Lang.makeThrow("No columns be queryed: '%s'", _en(en)); + } sb.setCharAt(sb.length() - 1, ' '); } } + + /** + * 设置安全的表名 + * @param tableName + * @return + */ + protected String safeTableName(String tableName) { + if (!Daos.CHECK_COLUMN_NAME_KEYWORD) { + //return tableName; + } + String str = expert.wrapKeyword(tableName, Daos.FORCE_WRAP_COLUMN_NAME); + return str == null ? tableName : str; + } } diff --git a/src/org/nutz/dao/impl/sql/NutSql.java b/src/org/nutz/dao/impl/sql/NutSql.java index dc69acc9bb..2d550a4c15 100644 --- a/src/org/nutz/dao/impl/sql/NutSql.java +++ b/src/org/nutz/dao/impl/sql/NutSql.java @@ -5,7 +5,7 @@ import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; -import java.util.Arrays; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -43,12 +43,17 @@ public class NutSql extends NutStatement implements Sql { protected List items; protected char[] placeholder; + public NutSql() { + this(null, null); + } + public NutSql(String source) { this(source, null); } public NutSql(String source, SqlCallback callback) { - this.setSourceSql(source); + if (source != null) + this.setSourceSql(source); this.callback = callback; this.vars = new SimpleVarSet(); this.rows = new ArrayList(); @@ -58,7 +63,7 @@ public NutSql(String source, SqlCallback callback) { } public void setSourceSql(String sql) { - this.sourceSql = sql; + this.sourceSql = sql.trim(); SqlLiteral literal = literal(); this.varIndex = literal.getVarIndexes(); this.paramIndex = literal.getParamIndexes(); @@ -82,12 +87,13 @@ public void setSourceSql(String sql) { } } } + this.items = new ArrayList(); for (int i = 0; i < tmp.length; i++) { if (tmp[i] == null) { tmp[i] = new StaticPItem(ss[i], true); } + items.add(tmp[i]); } - this.items = Arrays.asList(tmp); } protected int _params_count() { @@ -199,7 +205,7 @@ public String getSourceSql() { class SqlVarPItem extends AbstractPItem { /** - * + * */ private static final long serialVersionUID = 2655530650031939556L; public String name; @@ -221,7 +227,7 @@ else if (val instanceof Condition) { } } } - + public int joinAdaptor(Entity en, ValueAdaptor[] adaptors, int off) { Object val = vars.get(name); if (val != null) { @@ -231,7 +237,7 @@ public int joinAdaptor(Entity en, ValueAdaptor[] adaptors, int off) { } return off; } - + public int paramCount(Entity en) { Object val = vars.get(name); if (val != null) { @@ -241,7 +247,7 @@ public int paramCount(Entity en) { } return 0; } - + public int joinParams(Entity en, Object obj, Object[] params, int off) { Object val = vars.get(name); if (val != null) { @@ -256,7 +262,7 @@ public int joinParams(Entity en, Object obj, Object[] params, int off) { class SqlParamPItem extends AbstractPItem { /** - * + * */ private static final long serialVersionUID = 1494513192752663060L; public String name; @@ -271,7 +277,7 @@ public void joinSql(Entity en, StringBuilder sb) { sb.append("?"); } else if (val instanceof PItem) { ((PItem) val).joinSql(en, sb); - } else if (val.getClass().isArray()) { + } else if (val.getClass().isArray() || Collection.class.isAssignableFrom(val.getClass())) { sb.append(Strings.dup("?,", Lang.eleSize(val))); sb.setLength(sb.length() - 1); } else if (val instanceof Condition) { @@ -302,7 +308,7 @@ public int joinAdaptor(final Entity en, final ValueAdaptor[] adaptors, final return off + 1; } else if (val instanceof PItem) { return ((PItem) val).joinAdaptor(en, adaptors, off); - } else if (val.getClass().isArray()) { + } else if (val.getClass().isArray() || Collection.class.isAssignableFrom(val.getClass())) { int len = Lang.eleSize(val); Lang.each(val, new Each() { public void invoke(int index, Object ele, int length) { @@ -325,7 +331,7 @@ public int joinParams(Entity en, Object obj, final Object[] params, final int return off + 1; } else if (val instanceof PItem) { return ((PItem) val).joinParams(en, null, params, off); - } else if (val.getClass().isArray()) { + } else if (val.getClass().isArray() || Collection.class.isAssignableFrom(val.getClass())) { int len = Lang.eleSize(val); Lang.each(val, new Each() { public void invoke(int index, Object ele, int length) { @@ -347,7 +353,7 @@ public int paramCount(Entity en) { return 1; } else if (val instanceof PItem) { return ((PItem) val).paramCount(en); - } else if (val.getClass().isArray()) { + } else if (val.getClass().isArray() || Collection.class.isAssignableFrom(val.getClass())) { return Lang.eleSize(val); } else if (val instanceof Condition) { return 0; @@ -356,7 +362,7 @@ public int paramCount(Entity en) { } } } - + /** * 若需要定制参数字符和变量字符,覆盖本方法,通过SqlLiteral的构造方法指定之 */ @@ -365,24 +371,40 @@ protected SqlLiteral literal() { return new SqlLiteral().valueOf(sourceSql); return new SqlLiteral(placeholder[0], placeholder[1]).valueOf(sourceSql); } - + public Sql setParam(String name, Object value) { params().set(name, value); return this; } - + + public Sql setParams(Map params) { + params().putAll(params); + return this; + } + public Sql setVar(String name, Object value) { vars().set(name, value); return this; } - + + public Sql setVars(Map vars) { + vars().putAll(vars); + return this; + } + public Record getOutParams() { return getContext().attr(Record.class, "OUT"); } - + public Sql changePlaceholder (char param, char var) { placeholder = new char[]{param, var}; setSourceSql(getSourceSql()); return null; } + + public Sql appendSourceSql(String ext) { + if (ext != null) + setSourceSql(getSourceSql() + " " + ext); + return this; + } } diff --git a/src/org/nutz/dao/impl/sql/NutStatement.java b/src/org/nutz/dao/impl/sql/NutStatement.java index ced9eb3299..8b29053d04 100644 --- a/src/org/nutz/dao/impl/sql/NutStatement.java +++ b/src/org/nutz/dao/impl/sql/NutStatement.java @@ -29,13 +29,13 @@ public abstract class NutStatement implements DaoStatement { private static final long serialVersionUID = 1L; - private Entity entity; + private transient Entity entity; - private SqlContext context; + private transient SqlContext context; private SqlType sqlType; - protected JdbcExpert expert; + protected transient JdbcExpert expert; public NutStatement() { this.context = new SqlContext(); @@ -339,4 +339,9 @@ protected ValueAdaptor getAdapterBy(Object value) { Jdbcs.guessEntityFieldColumnType(mf); return expert.getAdaptor(mf); } + + public DaoStatement setQueryTimeout(int timeout) { + getContext().setQueryTimeout(timeout); + return this; + } } diff --git a/src/org/nutz/dao/impl/sql/SqlLiteral.java b/src/org/nutz/dao/impl/sql/SqlLiteral.java index 3e3afb5af5..3a5beaa3c3 100644 --- a/src/org/nutz/dao/impl/sql/SqlLiteral.java +++ b/src/org/nutz/dao/impl/sql/SqlLiteral.java @@ -146,6 +146,16 @@ else if (stack.firstEquals("{CALL")) } private static int readTokenName(char[] cs, int i, StringBuilder sb) { + // 自定义SQL 支持 ${abc} @{abc} 这种形式 + if (cs[i+1] == '{') { + for (i+=2; i < cs.length; i++) { + if (cs[i] == '}') + return i; + else + sb.append(cs[i]); + } + return i; + } for (++i; i < cs.length; i++) { int b = (int) cs[i]; // Special case for underline ('_') @@ -154,7 +164,7 @@ private static int readTokenName(char[] cs, int i, StringBuilder sb) { } // 遇到了 '$' else if (b == 36) { - return i; + return i; // 事实上这里是BUG, 应该返回i-1, 应不应该fix呢? } // 正常的不可忽略的字符 else if ((b >= 0 && b <= 47) diff --git a/src/org/nutz/dao/impl/sql/SqlTemplate.java b/src/org/nutz/dao/impl/sql/SqlTemplate.java index c0eb24706f..c6c9fbdc9b 100644 --- a/src/org/nutz/dao/impl/sql/SqlTemplate.java +++ b/src/org/nutz/dao/impl/sql/SqlTemplate.java @@ -22,7 +22,7 @@ /** * 仿照Spring JdbcTemplate实现nutz的SqlTemplate,方便sql的调用 - * + * * @author hzl7652(hzl7652@sina.com) */ public class SqlTemplate { @@ -45,12 +45,12 @@ public Dao dao() { /** * 执行一个SQL更新操作(如插入,更新或删除语句)。 - * + * * @param sql * 包含变量占位符的SQL * @param params * 参数map,无参数时,可为null - * + * * @return SQL 语句所影响的行数 */ public int update(String sql, Map params) { @@ -59,14 +59,14 @@ public int update(String sql, Map params) { /** * 执行一个SQL更新操作(如插入,更新或删除语句)。 - * + * * @param sql * 包含变量占位符的SQL * @param vars * 变量map,无参数时,可为null * @param params * 参数map,无参数时,可为null - * + * * @return SQL 语句所影响的行数 */ public int update(String sql, Map vars, Map params) { @@ -77,12 +77,12 @@ public int update(String sql, Map vars, Map para /** * 执行SQL批量更新操作(如插入,更新或删除语句)。 - * + * * @param sql * 包含变量占位符的SQL * @param batchValues * 批量更新参数集合 - * + * * @return SQL 语句所影响的行数 */ public int batchUpdate(String sql, List> batchValues) { @@ -91,14 +91,14 @@ public int batchUpdate(String sql, List> batchValues) { /** * 执行SQL批量更新操作(如插入,更新或删除语句)。 - * + * * @param sql * 包含变量占位符的SQL * @param vars * 变量map,无参数时,可为null * @param batchValues * 批量更新参数集合 - * + * * @return SQL 语句所影响的行数 */ public int batchUpdate(String sql, @@ -124,12 +124,12 @@ public int batchUpdate(String sql, /** * 执行一个SQL查询操作,结果为一个int形数值。 *

- * + * * @param sql * 包含变量占位符的SQL * @param params * 参数map,无参数时,可为null - * + * * @return int数值,当查询为null时返回0 */ public int queryForInt(String sql, Map params) { @@ -138,14 +138,14 @@ public int queryForInt(String sql, Map params) { /** * 执行一个SQL查询操作,结果为一个int形数值。 - * + * * @param sql * 包含变量占位符的SQL * @param vars * 变量map,无参数时,可为null * @param params * 参数map,无参数时,可为null - * + * * @return int数值,当查询为null时返回0 */ public int queryForInt(String sql, Map vars, Map params) { @@ -157,12 +157,12 @@ public int queryForInt(String sql, Map vars, Map /** * 执行一个SQL查询操作,结果为一个long形数值。 - * + * * @param sql * 包含变量占位符的SQL * @param params * 参数map,无参数时,可为null - * + * * @return long数值,当查询为null时返回0 */ public long queryForLong(String sql, Map params) { @@ -171,14 +171,14 @@ public long queryForLong(String sql, Map params) { /** * 执行一个SQL查询操作,结果为一个long形数值。 - * + * * @param sql * 包含变量占位符的SQL * @param vars * 变量map,无参数时,可为null * @param params * 参数map,无参数时,可为null - * + * * @return long数值,当查询为null时返回0 */ public long queryForLong(String sql, Map vars, Map params) { @@ -191,14 +191,14 @@ public long queryForLong(String sql, Map vars, Map T queryForObject(String sql, Map params, Class classOfT) { @@ -207,7 +207,7 @@ public T queryForObject(String sql, Map params, Class cla /** * 执行一个SQL查询操作,结果为给定对象类型的对象,适用于明确SQL查询结果的类型。 - * + * * @param sql * 包含变量占位符的SQL * @param vars @@ -216,7 +216,7 @@ public T queryForObject(String sql, Map params, Class cla * 参数map,无参数时,可为null * @param classOfT * 对象类型,SQL查询结果所对应的类型,如Date.class等 - * + * * @return 对象,无查询结果时返回null */ public T queryForObject(String sql, @@ -238,14 +238,14 @@ public Object invoke(Connection conn, ResultSet rs, Sql sql) throws SQLException /** * 执行一个SQL查询操作,结果为给定实体的对象。 - * + * * @param sql * 包含变量占位符的SQL * @param params * 参数map,无参数时,可为null * @param entity * 实体类型,无参数时,可为null - * + * * @return 对象,无查询结果时返回null */ public T queryForObject(String sql, Map params, Entity entity) { @@ -254,7 +254,7 @@ public T queryForObject(String sql, Map params, Entity en /** * 执行一个SQL查询操作,结果为给定实体的对象。 - * + * * @param sql * 包含变量占位符的SQL * @param vars @@ -263,7 +263,7 @@ public T queryForObject(String sql, Map params, Entity en * 参数map,无参数时,可为null * @param entity * 实体类型 - * + * * @return 对象,无查询结果时返回null */ public T queryForObject(String sql, @@ -281,12 +281,12 @@ public T queryForObject(String sql, /** * 执行一个SQL查询操作,结果为Record的对象。 - * + * * @param sql * 包含变量占位符的SQL * @param params * 参数map,无参数时,可为null - * + * * @return Record对象,无查询结果时返回null */ public Record queryForRecord(String sql, Map params) { @@ -295,7 +295,7 @@ public Record queryForRecord(String sql, Map params) { /** * 执行一个SQL查询操作,结果为Record的对象。 - * + * * @param sql * 包含变量占位符的SQL * @param vars @@ -315,14 +315,14 @@ public Record queryForRecord(String sql, Map vars, Map List query(String sql, Map params, Entity entity) { @@ -331,14 +331,14 @@ public List query(String sql, Map params, Entity entit /** * 执行一个SQL查询操作,结果为一组对象。 - * + * * @param sql * 包含变量占位符的SQL * @param params * 参数map,无参数时,可为null * @param classOfT * 对象类类 - * + * * @return 对象列表,无查询结果时返回长度为0的List对象 */ public List query(String sql, @@ -349,7 +349,7 @@ public List query(String sql, /** * 执行一个SQL查询操作,结果为一组对象。 - * + * * @param sql * 包含变量占位符的SQL * @param vars @@ -358,7 +358,7 @@ public List query(String sql, * 参数map,无参数时,可为null * @param entity * 对象类型 - * + * * @return 对象列表,无查询结果时返回长度为0的List对象 */ public List query(String sql, @@ -376,7 +376,7 @@ public List query(String sql, /** * 执行一个SQL查询操作,结果为一组对象。 - * + * * @param sql * 包含变量占位符的SQL * @param vars @@ -385,7 +385,7 @@ public List query(String sql, * 参数map,无参数时,可为null * @param classOfT * 对象类型 - * + * * @return 对象列表,无查询结果时返回长度为0的List对象 */ public List queryForList(String sql, @@ -412,14 +412,14 @@ public Object invoke(Connection conn, ResultSet rs, Sql sql) throws SQLException /** * 执行一个SQL查询操作,结果为Record对象列表。 - * + * * @param sql * 包含变量占位符的SQL * @param vars * 变量map,无参数时,可为null * @param params * 参数map,无参数时,可为null - * + * * @return Record列表,无查询结果时返回长度为0的List对象 */ public List queryRecords(String sql, @@ -452,12 +452,12 @@ private void execute(Sql sqlObj, Map vars, Map p * 创建Sql对象。 *

* 在这里处理Array Collection类型参数,方便SQL IN 表达式的设置 - * + * * @param sql * 包含变量占位符的SQL * @param params * 参数map,无参数时,可为null - * + * * @return Sql对象 */ private Sql createSqlObj(String sql, Map params) { @@ -472,12 +472,12 @@ private Sql createSqlObj(String sql, Map params) { /** * 将Array Collection类型参数对应的sql占位符进行处理 - * + * * @param originSql * 原包含变量占位符的SQL * @param params * 参数map,无参数时,可为null - * + * * @return 包含处理IN表达式的sql */ private String sqlProcess(String originSql, Map params) { @@ -490,6 +490,10 @@ private String sqlProcess(String originSql, Map params) { String paramName = entry.getKey(); Object paramObj = entry.getValue(); + if(paramObj == null){ + continue; + } + if (paramObj.getClass().isArray()) { String inSqlExp = inSqlProcess(paramName, paramObj); newSql = newSql.replaceAll("@" + paramName, inSqlExp); @@ -509,10 +513,10 @@ private String sqlProcess(String originSql, Map params) { /** * sql参数处理,在这里处理Array Collection类型参数,方便SQL IN 表达式的设置 - * + * * @param params * 参数map,无参数时,可为null - * + * * @return 包含处理IN表达式的sql */ private Map paramProcess(Map params) { @@ -523,6 +527,10 @@ private Map paramProcess(Map params) { String paramName = entry.getKey(); Object paramObj = entry.getValue(); + if(paramObj == null){ + continue; + } + if (paramObj.getClass().isArray()) { inParamProcess(paramName, paramObj, newParams); newParams.remove(paramName); diff --git a/src/org/nutz/dao/impl/sql/callback/FetchBlobCallback.java b/src/org/nutz/dao/impl/sql/callback/FetchBlobCallback.java new file mode 100644 index 0000000000..8b69da186f --- /dev/null +++ b/src/org/nutz/dao/impl/sql/callback/FetchBlobCallback.java @@ -0,0 +1,23 @@ +package org.nutz.dao.impl.sql.callback; + +import org.nutz.dao.impl.jdbc.BlobValueAdaptor; +import org.nutz.dao.jdbc.Jdbcs; +import org.nutz.dao.sql.Sql; +import org.nutz.dao.sql.SqlCallback; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * 这个回调将返回一个 Blob 值 + * @author wizzercn(wizzer.cn@gmail.com) + */ +public class FetchBlobCallback implements SqlCallback { + + public Object invoke(Connection conn, ResultSet rs, Sql sql) throws SQLException { + if (null != rs && rs.next()) + return new BlobValueAdaptor(Jdbcs.getFilePool()).get(rs, 1); + return null; + } +} diff --git a/src/org/nutz/dao/impl/sql/callback/QueryMapCallback.java b/src/org/nutz/dao/impl/sql/callback/QueryMapCallback.java index a53f910cae..62dde5f20f 100644 --- a/src/org/nutz/dao/impl/sql/callback/QueryMapCallback.java +++ b/src/org/nutz/dao/impl/sql/callback/QueryMapCallback.java @@ -19,7 +19,7 @@ public class QueryMapCallback implements SqlCallback { public Object invoke(Connection conn, ResultSet rs, Sql sql) throws SQLException { final ResultSetMetaData meta = rs.getMetaData(); - // ResultSetLooping 封装了遍历结果集的方法,里面包含了针对sqlserver等浮标型分页的支持 + // ResultSetLooping 封装了遍历结果集的方法,里面包含了针对sql server等浮标型分页的支持 ResultSetLooping ing = new ResultSetLooping() { protected boolean createObject(int index, ResultSet rs, diff --git a/src/org/nutz/dao/impl/sql/pojo/AbstractPItem.java b/src/org/nutz/dao/impl/sql/pojo/AbstractPItem.java index 94473054b6..f9e8043796 100644 --- a/src/org/nutz/dao/impl/sql/pojo/AbstractPItem.java +++ b/src/org/nutz/dao/impl/sql/pojo/AbstractPItem.java @@ -11,7 +11,7 @@ public abstract class AbstractPItem implements PItem { private static final long serialVersionUID = 1L; - protected Pojo pojo; + protected transient Pojo pojo; protected boolean top = true; diff --git a/src/org/nutz/dao/impl/sql/pojo/PojoFetchEntityByJoinCallback.java b/src/org/nutz/dao/impl/sql/pojo/PojoFetchEntityByJoinCallback.java index 8a1d970598..b177914f13 100644 --- a/src/org/nutz/dao/impl/sql/pojo/PojoFetchEntityByJoinCallback.java +++ b/src/org/nutz/dao/impl/sql/pojo/PojoFetchEntityByJoinCallback.java @@ -27,7 +27,7 @@ public Object invoke(Connection conn, final ResultSet rs, Pojo pojo, Statement s pojo.getEntity().visitOne(mainObject, regex, new LinkVisitor() { public void visit(Object obj, LinkField lnk) { Entity en = lnk.getLinkedEntity(); - String prefix = en.getTableName() + "_z_"; + String prefix = lnk.getName() + "_z_"; Object linkObject = en.getObject(rs, FieldFilter.get(en.getType()), prefix); lnk.setValue(mainObject, linkObject); } diff --git a/src/org/nutz/dao/impl/sql/pojo/PojoQueryEntityByJoinCallback.java b/src/org/nutz/dao/impl/sql/pojo/PojoQueryEntityByJoinCallback.java index f712811e26..9f94ba4745 100644 --- a/src/org/nutz/dao/impl/sql/pojo/PojoQueryEntityByJoinCallback.java +++ b/src/org/nutz/dao/impl/sql/pojo/PojoQueryEntityByJoinCallback.java @@ -30,7 +30,7 @@ protected boolean createObject(int index, final ResultSet rs, SqlContext context pojo.getEntity().visitOne(mainObject, regex, new LinkVisitor() { public void visit(Object obj, LinkField lnk) { Entity en = lnk.getLinkedEntity(); - String prefix = en.getTableName() + "_z_"; + String prefix = lnk.getName() + "_z_"; Object linkObject = en.getObject(rs, FieldFilter.get(en.getType()), prefix); lnk.setValue(mainObject, linkObject); } diff --git a/src/org/nutz/dao/impl/sql/pojo/UpdateFieldsByChainPItem.java b/src/org/nutz/dao/impl/sql/pojo/UpdateFieldsByChainPItem.java index 0c8f78ccfc..3cf1123cf2 100644 --- a/src/org/nutz/dao/impl/sql/pojo/UpdateFieldsByChainPItem.java +++ b/src/org/nutz/dao/impl/sql/pojo/UpdateFieldsByChainPItem.java @@ -17,6 +17,7 @@ public UpdateFieldsByChainPItem(Chain chain) { this.chain = chain; } + @Override public void joinSql(Entity en, StringBuilder sb) { if (chain.size() > 0) { sb.append(" SET "); @@ -29,16 +30,17 @@ public void joinSql(Entity en, StringBuilder sb) { String str = (String) head.value(); if (str.length() > 0) { switch (str.charAt(0)) { - case '+': - case '-': - case '*': - case '/': - case '%': - case '&': - case '^': - case '|': - sb.append(this._fmtcolnm(en, c.name())); - break; + case '+': + case '-': + case '*': + case '/': + case '%': + case '&': + case '^': + case '|': + sb.append(this._fmtcolnm(en, c.name())); + break; + default: } } } @@ -56,38 +58,44 @@ public void joinSql(Entity en, StringBuilder sb) { } } + @Override public int joinAdaptor(Entity en, ValueAdaptor[] adaptors, int off) { Chain c = chain.head(); while (c != null) { if (!c.special()) { MappingField mf = en.getField(c.name()); // TODO 移除这种数组下标用++的写法!!! - if (c.adaptor() == null) + if (c.adaptor() == null) { adaptors[off++] = (null == mf ? Jdbcs.getAdaptorBy(c.value()) : mf.getAdaptor()); - else + } else { adaptors[off++] = c.adaptor(); + } } c = c.next(); } return off; } + @Override public int joinParams(Entity en, Object obj, Object[] params, int off) { Chain c = chain.head(); while (c != null) { - if (!c.special()) + if (!c.special()) { params[off++] = c.value(); + } c = c.next(); } return off; } + @Override public int paramCount(Entity en) { int count = 0; Chain c = chain.head(); while (c != null) { - if (!c.special()) + if (!c.special()) { count++; + } c = c.next(); } return count; diff --git a/src/org/nutz/dao/impl/sql/run/NutDaoExecutor.java b/src/org/nutz/dao/impl/sql/run/NutDaoExecutor.java index 89674f285d..b0592a216e 100644 --- a/src/org/nutz/dao/impl/sql/run/NutDaoExecutor.java +++ b/src/org/nutz/dao/impl/sql/run/NutDaoExecutor.java @@ -26,14 +26,20 @@ import org.nutz.dao.sql.VarIndex; import org.nutz.dao.sql.VarSet; import org.nutz.dao.util.Daos; +import org.nutz.lang.Configurable; import org.nutz.lang.Lang; +import org.nutz.lang.util.NutMap; import org.nutz.log.Log; import org.nutz.log.Logs; -public class NutDaoExecutor implements DaoExecutor { +public class NutDaoExecutor implements DaoExecutor, Configurable { private static final Log log = Logs.get(); + protected int defaultQueryTimeout; + + protected int defaultFetchSize; + public void exec(Connection conn, DaoStatement st) { // 这个变量声明,后面两 case 要用到 Object[][] paramMatrix; @@ -78,8 +84,8 @@ public void exec(Connection conn, DaoStatement st) { _runSelect(conn, st); break; } - if (st.getSqlType() == SqlType.OTHER && log.isInfoEnabled()) - log.info("Can't identify SQL type : " + st); + if (st.getSqlType() == SqlType.OTHER && log.isDebugEnabled()) + log.debugf("Can't identify SQL type : %s", st); paramMatrix = st.getParamMatrix(); // 木有参数,直接运行 if (null == paramMatrix || paramMatrix.length == 0) { @@ -91,7 +97,7 @@ public void exec(Connection conn, DaoStatement st) { } } } - // If any SQLException happend, throw out the SQL string + // If any SQLException happened, throw out the SQL string catch (SQLException e) { if (log.isDebugEnabled()) { log.debug("SQLException", e); @@ -233,7 +239,6 @@ private void _runSelect(Connection conn, DaoStatement st) ResultSet rs = null; Statement stat = null; try { - // 木有参数,直接运行 if (null == paramMatrix || paramMatrix.length == 0 || paramMatrix[0].length == 0) { @@ -241,8 +246,7 @@ private void _runSelect(Connection conn, DaoStatement st) .getResultSetType(), ResultSet.CONCUR_READ_ONLY); if (lastRow > 0) stat.setMaxRows(lastRow); // 游标分页,现在总行数 - if (st.getContext().getFetchSize() != 0) - stat.setFetchSize(st.getContext().getFetchSize()); + afterCreateStatement(stat, st); rs = stat.executeQuery(sql); } // 有参数,用缓冲语句 @@ -263,8 +267,7 @@ private void _runSelect(Connection conn, DaoStatement st) ResultSet.CONCUR_READ_ONLY); if (lastRow > 0) stat.setMaxRows(lastRow); - if (st.getContext().getFetchSize() != 0) - stat.setFetchSize(st.getContext().getFetchSize()); + afterCreateStatement(stat, st); for (int i = 0; i < paramMatrix[0].length; i++) { adaptors[i].set((PreparedStatement) stat, paramMatrix[0][i], i + 1); @@ -399,4 +402,39 @@ public OutParam(String name, int jdbcType) { this.jdbcType = jdbcType; } } + + protected void afterCreateStatement(Statement stat, DaoStatement st) throws SQLException { + if (st.getContext().getFetchSize() != 0) + stat.setFetchSize(st.getContext().getFetchSize()); + else if (defaultFetchSize > 0) { + stat.setFetchSize(defaultFetchSize); + } + if (st.getContext().getQueryTimeout() > 0) + stat.setQueryTimeout(st.getContext().getQueryTimeout()); + else if (defaultQueryTimeout > 0) { + stat.setQueryTimeout(defaultQueryTimeout); + } + } + + public int getDefaultQueryTimeout() { + return defaultQueryTimeout; + } + + public void setDefaultQueryTimeout(int defaultQueryTimeout) { + this.defaultQueryTimeout = defaultQueryTimeout; + } + + public int getDefaultFetchSize() { + return defaultFetchSize; + } + + public void setDefaultFetchSize(int defaultFetchSize) { + this.defaultFetchSize = defaultFetchSize; + } + + public void setupProperties(NutMap conf) { + defaultQueryTimeout = conf.getInt("nutz.dao.query.timeout", 0); + defaultFetchSize = conf.getInt("nutz.dao.query.fetchSize", 0); + } + } diff --git a/src/org/nutz/dao/impl/sql/run/NutDaoRunner.java b/src/org/nutz/dao/impl/sql/run/NutDaoRunner.java index d63fdbf8f3..7614f35f91 100644 --- a/src/org/nutz/dao/impl/sql/run/NutDaoRunner.java +++ b/src/org/nutz/dao/impl/sql/run/NutDaoRunner.java @@ -10,8 +10,11 @@ import org.nutz.dao.DaoException; import org.nutz.dao.DaoInterceptorChain; import org.nutz.dao.DatabaseMeta; +import org.nutz.dao.DB; import org.nutz.dao.impl.DaoRunner; import org.nutz.dao.sql.DaoStatement; +import org.nutz.lang.Configurable; +import org.nutz.lang.util.NutMap; import org.nutz.log.Log; import org.nutz.log.Logs; import org.nutz.trans.Atom; @@ -23,16 +26,19 @@ * @author wendal * */ -public class NutDaoRunner implements DaoRunner { +public class NutDaoRunner implements DaoRunner, Configurable { private static final Log log = Logs.get(); protected DataSource slaveDataSource; + protected boolean supportSavePoint = true; + public void run(final DataSource dataSource, final ConnCallback callback) { if (callback instanceof DaoInterceptorChain) { + DaoInterceptorChain chain = (DaoInterceptorChain)callback; // 看看是不是应该强制使用事务 - DaoStatement[] sts = ((DaoInterceptorChain)callback).getDaoStatements(); + DaoStatement[] sts = chain.getDaoStatements(); boolean useTrans = false; boolean isAllSelect = true; for (DaoStatement st : sts) { @@ -53,7 +59,7 @@ public void run(final DataSource dataSource, final ConnCallback callback) { if (isAllSelect) useTrans = false; else { - ((DaoInterceptorChain) callback).setAutoTransLevel(Connection.TRANSACTION_READ_UNCOMMITTED); + chain.setAutoTransLevel(Connection.TRANSACTION_READ_UNCOMMITTED); useTrans = true; } } @@ -68,8 +74,8 @@ else if (t.getLevel() != Connection.TRANSACTION_SERIALIZABLE break; } // 看来需要开启事务了 - if (useTrans) { - Trans.exec(((DaoInterceptorChain) callback).getAutoTransLevel(), new Atom() { + if (useTrans && chain.getAutoTransLevel() > 0) { + Trans.exec(chain.getAutoTransLevel(), new Atom() { public void run() { _run(dataSource, callback); } @@ -98,7 +104,7 @@ protected void _runWithTransaction(Transaction t, DataSource dataSource, ConnCal Savepoint sp = null; try { conn = t.getConnection(selectDataSource(t, dataSource, callback)); - if (meta != null && meta.isPostgresql()) { + if (supportSavePoint && meta != null && meta.isPostgresql()) { sp = conn.setSavepoint(); } runCallback(conn, callback); @@ -171,13 +177,28 @@ public void setSlaveDataSource(DataSource slaveDataSource) { protected DataSource selectDataSource(Transaction t, DataSource master, ConnCallback callback) { if (this.slaveDataSource == null) return master; - if (t == null && callback instanceof DaoInterceptorChain) { - DaoInterceptorChain chain = (DaoInterceptorChain)callback; - DaoStatement[] sts = chain.getDaoStatements(); - if (sts.length == 1 && (sts[0].isSelect() || sts[0].isForceExecQuery())) { - return slaveDataSource; + if(meta.getType() == DB.PSQL){ + if (callback instanceof DaoInterceptorChain) { + DaoInterceptorChain chain = (DaoInterceptorChain)callback; + DaoStatement[] sts = chain.getDaoStatements(); + if (sts.length == 1 && (sts[0].isSelect() || sts[0].isForceExecQuery())) { + return slaveDataSource; + } + } + }else { + if (t == null && callback instanceof DaoInterceptorChain) { + DaoInterceptorChain chain = (DaoInterceptorChain)callback; + DaoStatement[] sts = chain.getDaoStatements(); + if (sts.length == 1 && (sts[0].isSelect() || sts[0].isForceExecQuery())) { + return slaveDataSource; + } } } return master; } + + @Override + public void setupProperties(NutMap conf) { + supportSavePoint = conf.getBoolean("nutz.dao.jdbc.psql.supportSavePoint", true); + } } diff --git a/src/org/nutz/dao/interceptor/PojoInterceptor.java b/src/org/nutz/dao/interceptor/PojoInterceptor.java new file mode 100644 index 0000000000..1864207e38 --- /dev/null +++ b/src/org/nutz/dao/interceptor/PojoInterceptor.java @@ -0,0 +1,19 @@ +package org.nutz.dao.interceptor; + +import org.nutz.dao.entity.Entity; +import org.nutz.dao.jdbc.JdbcExpert; + +public interface PojoInterceptor { + + /** + * 拦截并返回对象, 如无改变, 返回原对象就行 + */ + void onEvent(Object obj, Entity en, String event, Object... args); + + void setupEntity(Entity en, JdbcExpert expert); + + /** + * 当前拦截器是否可用,用于避免多余的调用 + */ + boolean isAvailable(); +} diff --git a/src/org/nutz/dao/interceptor/annotation/PrevDelete.java b/src/org/nutz/dao/interceptor/annotation/PrevDelete.java new file mode 100644 index 0000000000..c857260811 --- /dev/null +++ b/src/org/nutz/dao/interceptor/annotation/PrevDelete.java @@ -0,0 +1,25 @@ +package org.nutz.dao.interceptor.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; + +import org.nutz.dao.entity.annotation.EL; + +/** + * 在执行删除操作时触发 + * @author wendal + * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.METHOD}) +@Documented +public @interface PrevDelete { + + /** + * 执行一个EL表达式,如果返回值不是null,赋值到当前字段 + */ + EL[] els() default {}; +} diff --git a/src/org/nutz/dao/interceptor/annotation/PrevInsert.java b/src/org/nutz/dao/interceptor/annotation/PrevInsert.java new file mode 100644 index 0000000000..c8a148e747 --- /dev/null +++ b/src/org/nutz/dao/interceptor/annotation/PrevInsert.java @@ -0,0 +1,40 @@ +package org.nutz.dao.interceptor.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; + +import org.nutz.dao.entity.annotation.EL; + +/** + * 在执行插入操作时触发 + * @author wendal + * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.METHOD}) +@Documented +public @interface PrevInsert { + + /** + * 执行一个EL表达式,如果返回值不是null,赋值到当前字段 + */ + EL[] els() default {}; + + /** + * 设置为当前时间,通常是createTime字段 + */ + boolean now() default false; + + /** + * 设置为UUID, 为nutz定义的UU32格式,通常配合@Name使用 + */ + boolean uu32() default false; + + /** + * nullEffective=true时上面的赋值规则要起效必须是在[当前字段==null]时才能生效 + */ + boolean nullEffective() default false; +} diff --git a/src/org/nutz/dao/interceptor/annotation/PrevUpdate.java b/src/org/nutz/dao/interceptor/annotation/PrevUpdate.java new file mode 100644 index 0000000000..08a8c6802e --- /dev/null +++ b/src/org/nutz/dao/interceptor/annotation/PrevUpdate.java @@ -0,0 +1,36 @@ +package org.nutz.dao.interceptor.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; + +import org.nutz.dao.entity.annotation.EL; + +/** + * 在执行更新操作时触发 + * @author wendal + * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.METHOD}) +@Documented +public @interface PrevUpdate { + + + /** + * 执行一个EL表达式,如果返回值不是null,赋值到当前字段 + */ + EL[] els() default {}; + + /** + * 设置为当前时间,通常是updateTime字段 + */ + boolean now() default false; + + /** + * nullEffective=true时上面的赋值规则要起效必须是在[当前字段==null]时才能生效 + */ + boolean nullEffective() default false; +} diff --git a/src/org/nutz/dao/interceptor/impl/BasicPojoInterceptor.java b/src/org/nutz/dao/interceptor/impl/BasicPojoInterceptor.java new file mode 100644 index 0000000000..4f32e3a9d0 --- /dev/null +++ b/src/org/nutz/dao/interceptor/impl/BasicPojoInterceptor.java @@ -0,0 +1,18 @@ +package org.nutz.dao.interceptor.impl; + +import org.nutz.dao.entity.Entity; +import org.nutz.dao.interceptor.PojoInterceptor; +import org.nutz.dao.jdbc.JdbcExpert; + +public class BasicPojoInterceptor implements PojoInterceptor { + + public void onEvent(Object obj, Entity en, String event, Object... args) { + } + + public void setupEntity(Entity en, JdbcExpert expert) { + } + + public boolean isAvailable() { + return true; + } +} diff --git a/src/org/nutz/dao/interceptor/impl/DefaultPojoInterceptor.java b/src/org/nutz/dao/interceptor/impl/DefaultPojoInterceptor.java new file mode 100644 index 0000000000..70566f326e --- /dev/null +++ b/src/org/nutz/dao/interceptor/impl/DefaultPojoInterceptor.java @@ -0,0 +1,96 @@ +package org.nutz.dao.interceptor.impl; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.util.LinkedList; +import java.util.List; + +import org.nutz.dao.DB; +import org.nutz.dao.entity.Entity; +import org.nutz.dao.entity.MappingField; +import org.nutz.dao.entity.annotation.EL; +import org.nutz.dao.interceptor.PojoInterceptor; +import org.nutz.dao.interceptor.annotation.PrevDelete; +import org.nutz.dao.interceptor.annotation.PrevInsert; +import org.nutz.dao.interceptor.annotation.PrevUpdate; +import org.nutz.dao.jdbc.JdbcExpert; + +public class DefaultPojoInterceptor extends BasicPojoInterceptor { + + protected List list = new LinkedList(); + + protected JdbcExpert expert; + + protected Entity en; + + public void setupEntity(Entity en, JdbcExpert expert) { + this.expert = expert; + this.en = en; + Field[] fields = en.getMirror().getFields(); + for (Field field : fields) { + MappingField mf = en.getField(field.getName()); + if (mf != null) + setupField(mf, field); + } + } + + protected void setupField(MappingField mf, Field field) { + for (Annotation anno : field.getAnnotations()) { + setupFieldAnnotation(mf, field, anno); + } + } + + protected void setupFieldAnnotation(MappingField mf, Field field, Annotation anno) { + if (anno instanceof PrevInsert) { + setupFieldEL(mf, field, ((PrevInsert)anno).els(), "prevInsert",((PrevInsert)anno).nullEffective()); + if (((PrevInsert)anno).now()) { + list.add(new SimpleElPojoInterceptor(mf, "now()", "prevInsert",((PrevInsert)anno).nullEffective())); + } + if (((PrevInsert)anno).uu32()) { + list.add(new SimpleElPojoInterceptor(mf, "uuid()", "prevInsert",((PrevInsert)anno).nullEffective())); + } + } + else if (anno instanceof PrevUpdate) { + setupFieldEL(mf, field, ((PrevUpdate)anno).els(), "prevUpdate",((PrevUpdate)anno).nullEffective()); + if (((PrevUpdate)anno).now()) { + list.add(new SimpleElPojoInterceptor(mf, "now()", "prevUpdate",((PrevUpdate)anno).nullEffective())); + } + } + else if (anno instanceof PrevDelete) { + setupFieldEL(mf, field, ((PrevDelete)anno).els(), "prevDelete",false); + } + } + + protected void setupFieldEL(MappingField mf, Field field, EL[] els, String event,boolean nullEffective) { + EL e = null; + for (EL el : els) { + if (el.db() == DB.OTHER && e == null) + e = el; + else if (el.db().name().equals(expert.getDatabaseType())) + e = el; + } + if (e != null) { + list.add(new SimpleElPojoInterceptor(mf, e.value(), event, nullEffective)); + } + } + + @Override + public void onEvent(Object obj, Entity en, String event, Object... args) { + for (PojoInterceptor pint : list) { + pint.onEvent(obj, en, event, args); + } + } + + @Override + public boolean isAvailable() { + return !list.isEmpty(); + } + + public List getList() { + return list; + } + + public void setList(List list) { + this.list = list; + } +} diff --git a/src/org/nutz/dao/interceptor/impl/SimpleElPojoInterceptor.java b/src/org/nutz/dao/interceptor/impl/SimpleElPojoInterceptor.java new file mode 100644 index 0000000000..ccd0c07b18 --- /dev/null +++ b/src/org/nutz/dao/interceptor/impl/SimpleElPojoInterceptor.java @@ -0,0 +1,73 @@ +package org.nutz.dao.interceptor.impl; + +import org.nutz.dao.entity.Entity; +import org.nutz.dao.entity.MappingField; +import org.nutz.dao.impl.entity.macro.ElFieldMacro; +import org.nutz.el.El; +import org.nutz.lang.Lang; +import org.nutz.lang.util.Context; +import org.nutz.log.Log; +import org.nutz.log.Logs; + +public class SimpleElPojoInterceptor extends BasicPojoInterceptor { + + protected static Log log = Logs.get(); + + protected String elStr; + + protected ElFieldMacro macro; + + protected String event; + + protected String selfStr; + + protected MappingField mf; + + protected El el; + + /** + * 当前字段如果不为默认值时才起效 + * 如果设置 nullEffective = true :则 当前字段==null 时起效 + */ + protected boolean nullEffective; + + protected SimpleElPojoInterceptor() { + } + + public SimpleElPojoInterceptor(MappingField mf, String str, String event) { + this.el = new El(str); + this.mf = mf; + this.event = event; + this.elStr = str; + this.selfStr = String.format("%s - %s - %s - %s - %s", mf.getEntity().getType().getSimpleName(), mf.getName(), event, this.elStr, this.nullEffective); + } + + public SimpleElPojoInterceptor(MappingField mf, String str, String event, boolean nullEffective) { + this.el = new El(str); + this.mf = mf; + this.event = event; + this.elStr = str; + this.nullEffective = nullEffective; + this.selfStr = String.format("%s - %s - %s - %s - %s", mf.getEntity().getType().getSimpleName(), mf.getName(), event, this.elStr, this.nullEffective); + } + + public void onEvent(Object obj, Entity en, String event, Object... args) { + if (event.equals(this.event)) { + if (this.nullEffective) + if (null != mf.getValue(obj)) + return; + Context context = Lang.context(); + context.set("field", mf.getColumnName()); + context.set("view", mf.getEntity()); + context.set("$me", obj); + Object elVal = el.eval(context); + if (elVal != null) + mf.setValue(obj, elVal); + } + } + + @Override + public String toString() { + return selfStr; + } +} diff --git a/src/org/nutz/dao/jdbc/JdbcExpert.java b/src/org/nutz/dao/jdbc/JdbcExpert.java index 2183155b18..448b5f0dfe 100644 --- a/src/org/nutz/dao/jdbc/JdbcExpert.java +++ b/src/org/nutz/dao/jdbc/JdbcExpert.java @@ -9,8 +9,10 @@ import org.nutz.dao.Dao; import org.nutz.dao.entity.Entity; import org.nutz.dao.entity.EntityIndex; +import org.nutz.dao.entity.LinkField; import org.nutz.dao.entity.MappingField; import org.nutz.dao.sql.DaoStatement; +import org.nutz.dao.sql.PItem; import org.nutz.dao.sql.Pojo; import org.nutz.dao.sql.Sql; import org.nutz.dao.sql.SqlType; @@ -109,8 +111,14 @@ public interface JdbcExpert { boolean supportTimestampDefault(); void setKeywords(Set keywords); - - String wrapKeywork(String columnName, boolean force); + + /** + * 关键字包装 + * @param columnName + * @param force + * @return + */ + String wrapKeyword(String columnName, boolean force); void checkDataSource(Connection conn) throws SQLException ; @@ -125,4 +133,6 @@ public interface JdbcExpert { boolean canCommentWhenAddIndex(); List getIndexNames(Entity en, Connection conn) throws SQLException; + + PItem formatLeftJoinLink(Object obj, LinkField lnk, Entity en); } diff --git a/src/org/nutz/dao/jdbc/Jdbcs.java b/src/org/nutz/dao/jdbc/Jdbcs.java index 69b1eadc1d..b990dc7bdb 100644 --- a/src/org/nutz/dao/jdbc/Jdbcs.java +++ b/src/org/nutz/dao/jdbc/Jdbcs.java @@ -22,8 +22,8 @@ import java.sql.SQLException; import java.sql.Timestamp; import java.sql.Types; -import java.util.Calendar; -import java.util.Map; +import java.time.*; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import javax.sql.DataSource; @@ -253,7 +253,10 @@ public static ValueAdaptor getAdaptor(Mirror mirror) { return Jdbcs.Adaptor.asBinaryStream; if (mirror.isOf(Reader.class)) return Jdbcs.Adaptor.asReader; - + if (mirror.isLocalDateLike()) + return Adaptor.asLocalDate; + if (mirror.isLocalDateTimeLike()) + return Jdbcs.Adaptor.asLocalDateTime; // 默认情况 return Jdbcs.Adaptor.asString; } @@ -760,6 +763,43 @@ public void set(PreparedStatement stat, Object obj, int index) throws SQLExcepti } } }; + + + public static final ValueAdaptor asLocalDateTime = new ValueAdaptor() { + + public Object get(ResultSet rs, String colName) throws SQLException { + Timestamp ts = rs.getTimestamp(colName); + return null == ts ? null : LocalDateTime.ofInstant(Instant.ofEpochMilli(ts.getTime()), ZoneId.systemDefault()); + } + + public void set(PreparedStatement stat, Object obj, int i) throws SQLException { + Timestamp v; + if (null == obj) { + stat.setNull(i, Types.TIMESTAMP); + } else { + v = Timestamp.valueOf((LocalDateTime)obj); + stat.setTimestamp(i, v); + } + } + }; + + public static final ValueAdaptor asLocalDate = new ValueAdaptor() { + + public Object get(ResultSet rs, String colName) throws SQLException { + Timestamp ts = rs.getTimestamp(colName); + return null == ts ? null : ts.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); + } + + public void set(PreparedStatement stat, Object obj, int i) throws SQLException { + Timestamp v; + if (null == obj) { + stat.setNull(i, Types.TIMESTAMP); + } else { + v = Timestamp.valueOf(((LocalDate)obj).atStartOfDay(ZoneId.systemDefault()).toLocalDateTime()); + stat.setTimestamp(i, v); + } + } + }; } /** @@ -769,7 +809,7 @@ public void set(PreparedStatement stat, Object obj, int index) throws SQLExcepti * 映射字段 */ public static void guessEntityFieldColumnType(NutMappingField ef) { - Mirror mirror = ef.getTypeMirror(); + Mirror mirror = ef.getMirror(); // 整型 if (mirror.isInt()) { @@ -814,7 +854,7 @@ else if (mirror.is(java.sql.Time.class)) { ef.setColumnType(ColType.TIME); } // 日期时间 - else if (mirror.isOf(Calendar.class) || mirror.is(java.util.Date.class)) { + else if (mirror.isOf(Calendar.class) || mirror.is(java.util.Date.class) || mirror.isLocalDateTimeLike()) { ef.setColumnType(ColType.DATETIME); } // 大数 @@ -854,7 +894,7 @@ else if (mirror.isOf(InputStream.class) * 上面的都不是? 那就当作字符串好了,反正可以 toString */ else { - if (log.isDebugEnabled()) + if (log.isDebugEnabled()&& ef.getEntity() != null && ef.getEntity().getType() != null) log.debugf("take field '%s(%s)'(%s) as VARCHAR(%d)", ef.getName(), Lang.getTypeClass(ef.getType()).getName(), @@ -886,6 +926,10 @@ public static void setCharacterStream(int index, Object obj, PreparedStatement s throw Lang.wrapThrow(e); } } + + public static JdbcExpertConfigFile getConf() { + return conf; + } } class ReadOnceInputStream extends FilterInputStream implements Serializable { diff --git a/src/org/nutz/dao/jdbc/nutz_jdbc_experts.js b/src/org/nutz/dao/jdbc/nutz_jdbc_experts.js index ab21ac5f31..dcc80901d7 100644 --- a/src/org/nutz/dao/jdbc/nutz_jdbc_experts.js +++ b/src/org/nutz/dao/jdbc/nutz_jdbc_experts.js @@ -8,10 +8,16 @@ var ioc = { */ experts : { "h2.*" : "org.nutz.dao.impl.jdbc.h2.H2JdbcExpert", + "dm dbms.*" : "org.nutz.dao.impl.jdbc.dm.DmJdbcExpert", + "dm mysql.*" : "org.nutz.dao.impl.jdbc.dm.DmMysqlJdbcExpert", "mysql.*" : "org.nutz.dao.impl.jdbc.mysql.MysqlJdbcExpert", + "mariadb.*" : "org.nutz.dao.impl.jdbc.mysql.MysqlJdbcExpert", "postgresql.*" : "org.nutz.dao.impl.jdbc.psql.PsqlJdbcExpert", "db2.*" : "org.nutz.dao.impl.jdbc.db2.Db2JdbcExpert", - "oracle.*" : "org.nutz.dao.impl.jdbc.oracle.OracleJdbcExpert", + "oracle.*" : "org.nutz.dao.impl.jdbc.oracle.OracleJdbcExpert", + "yashandb.*" : "org.nutz.dao.impl.jdbc.yashan.YashanJdbcExpert", + "kingbasees.*" : "org.nutz.dao.impl.jdbc.oracle.OracleJdbcExpert", + "kingbasepsql.*" : "org.nutz.dao.impl.jdbc.psql.PsqlJdbcExpert", // SqlServer2005 --> 9.0 , SqlServer2008 --> 10.0 "microsoft sql server.*(9|10)[.].+" : "org.nutz.dao.impl.jdbc.sqlserver2005.Sqlserver2005JdbcExpert", "microsoft sql server.*(8)[.].+" : "org.nutz.dao.impl.jdbc.sqlserver2000.Sqlserver2000JdbcExpert", @@ -21,7 +27,9 @@ var ioc = { ".+derby.+" : "org.nutz.dao.impl.jdbc.derby.DerbyJdbcExpert", "gbase.*" : "org.nutz.dao.impl.jdbc.gbase.GBaseJdbcExpert", "sybase.*" : "org.nutz.dao.impl.jdbc.sybase.SybaseIQJdbcExpert", - "dm dbms.*" : "org.nutz.dao.impl.jdbc.dm.DmJdbcExpert" + "tdengine.*" : "org.nutz.dao.impl.jdbc.tdengine.TDengineJdbcExpert", + "clickhouse.*" : "org.nutz.dao.impl.jdbc.clickhouse.ClickhouseJdbcExpert", + "xugu.*" : "org.nutz.dao.impl.jdbc.xugu.XuguJdbcExpert" // ~ 映射结束 }, @@ -29,7 +37,7 @@ var ioc = { * 所有 Expert 都能读到这个配置文件 */ config : { - // 默认的 Clob 以及 Blog 临时目录 + // 默认的 Clob/Blob 临时目录 "pool-home" : "~/.nutz/tmp/dao/", // 临时目录大小,0 为不限大小 "pool-max" : 200000, @@ -38,4 +46,4 @@ var ioc = { // GBase 特殊配置 "gbase-engine" : "GsDB" // ~ 配置信息结束 - } }; \ No newline at end of file + } }; diff --git a/src/org/nutz/dao/pager/Pager.java b/src/org/nutz/dao/pager/Pager.java index 511132ceea..eb52712e0d 100644 --- a/src/org/nutz/dao/pager/Pager.java +++ b/src/org/nutz/dao/pager/Pager.java @@ -35,17 +35,20 @@ public Pager() { } public Pager(int pageNumber) { - if (pageNumber < 1) + if (pageNumber < 1) { pageNumber = 1; + } this.pageNumber = pageNumber; this.pageSize = DEFAULT_PAGE_SIZE; } public Pager(int pageNumber, int pageSize) { - if (pageNumber < 1) + if (pageNumber < 1) { pageNumber = 1; - if (pageSize < 1) + } + if (pageSize < 1) { pageSize = DEFAULT_PAGE_SIZE; + } this.pageNumber = pageNumber; this.pageSize = pageSize; } @@ -55,42 +58,52 @@ public Pager resetPageCount() { return this; } + @Override public int getPageCount() { - if (pageCount < 0) + if (pageCount < 0) { pageCount = (int) Math.ceil((double) recordCount / pageSize); + } return pageCount; } + @Override public int getPageNumber() { return pageNumber; } + @Override public int getPageSize() { return pageSize; } + @Override public int getRecordCount() { return recordCount; } + @Override public Pager setPageNumber(int pn) { - if (1 > pn && log.isInfoEnabled()) + if (1 > pn && log.isInfoEnabled()) { log.infof("PageNumber shall start at 1, but input is %d, that mean pager is disable", pn); + } pageNumber = pn; return this; } + @Override public Pager setPageSize(int pageSize) { this.pageSize = (pageSize > 0 ? pageSize : DEFAULT_PAGE_SIZE); return resetPageCount(); } + @Override public Pager setRecordCount(int recordCount) { this.recordCount = recordCount > 0 ? recordCount : 0; this.pageCount = (int) Math.ceil((double) recordCount / pageSize); return this; } + @Override public int getOffset() { return pageSize * (pageNumber - 1); } @@ -104,20 +117,25 @@ public String toString() { this.getPageCount()); } + @Override public boolean isFirst() { return pageNumber == 1; } + @Override public boolean isLast() { - if (pageCount == 0) + if (pageCount == 0) { return true; + } return pageNumber == pageCount; } + @Override public boolean hasNext() { return !isLast(); } + @Override public boolean hasPrevious() { return !isFirst(); } diff --git a/src/org/nutz/dao/sql/DaoStatement.java b/src/org/nutz/dao/sql/DaoStatement.java index fa8a1d56ec..d6bbd10ccc 100644 --- a/src/org/nutz/dao/sql/DaoStatement.java +++ b/src/org/nutz/dao/sql/DaoStatement.java @@ -246,7 +246,7 @@ public interface DaoStatement extends Serializable { DaoStatement setPager(Pager pager); /** - * 如果sql的类型无法被nutz识别,而这个sql有的确是个查询,那么调用这个方法, 这样就强制nutz按select的方式执行 + * 如果sql的类型无法被nutz识别,而这个sql又的确是个查询,那么调用这个方法, 这样就强制nutz按select的方式执行 */ void forceExecQuery(); @@ -255,4 +255,6 @@ public interface DaoStatement extends Serializable { String forPrint(); void setExpert(JdbcExpert expert); + + DaoStatement setQueryTimeout(int timeout); } diff --git a/src/org/nutz/dao/sql/GroupBy.java b/src/org/nutz/dao/sql/GroupBy.java index 2e706cbbef..48201eaae7 100644 --- a/src/org/nutz/dao/sql/GroupBy.java +++ b/src/org/nutz/dao/sql/GroupBy.java @@ -1,10 +1,13 @@ package org.nutz.dao.sql; import org.nutz.dao.Condition; +import org.nutz.dao.util.lambda.PFun; public interface GroupBy extends OrderBy { GroupBy groupBy(String ... names); - + + GroupBy groupBy(PFun... names); + GroupBy having(Condition cnd); } diff --git a/src/org/nutz/dao/sql/OrderBy.java b/src/org/nutz/dao/sql/OrderBy.java index 060fde0d4f..3f7dd8b4be 100644 --- a/src/org/nutz/dao/sql/OrderBy.java +++ b/src/org/nutz/dao/sql/OrderBy.java @@ -1,13 +1,19 @@ package org.nutz.dao.sql; import org.nutz.dao.Condition; -import org.nutz.dao.sql.PItem; +import org.nutz.dao.util.lambda.PFun; public interface OrderBy extends Condition,PItem { OrderBy asc(String name); + OrderBy asc(PFun name); + OrderBy desc(String name); - + + OrderBy desc(PFun name); + OrderBy orderBy(String name, String dir); + + OrderBy orderBy(PFun name, String dir); } diff --git a/src/org/nutz/dao/sql/PojoMaker.java b/src/org/nutz/dao/sql/PojoMaker.java index 2e8d7a770a..5be7947775 100644 --- a/src/org/nutz/dao/sql/PojoMaker.java +++ b/src/org/nutz/dao/sql/PojoMaker.java @@ -23,4 +23,6 @@ public interface PojoMaker { Pojo makeFunc(String tableName, String funcName, String colName); Pojo makeQueryByJoin(Entity en, String regex); + + Pojo makeCountByJoin(Entity en, String regex); } diff --git a/src/org/nutz/dao/sql/Sql.java b/src/org/nutz/dao/sql/Sql.java index 29b6852d87..ab66bc9d6f 100644 --- a/src/org/nutz/dao/sql/Sql.java +++ b/src/org/nutz/dao/sql/Sql.java @@ -1,5 +1,7 @@ package org.nutz.dao.sql; +import java.util.Map; + import org.nutz.dao.Condition; import org.nutz.dao.entity.Entity; import org.nutz.dao.entity.Record; @@ -31,6 +33,14 @@ public interface Sql extends DaoStatement { */ Sql setVar(String name, Object value); + /** + * 批量设置vars + * @param vars 参数集合 + * @return 原Sql对象,用于链式调用 + * @see #params() + */ + Sql setVars(Map vars); + /** * 所谓"参数",就是当 Sql 对象转换成 PreparedStatement 对象前,会被填充成 ? 的占位符 *

@@ -50,6 +60,14 @@ public interface Sql extends DaoStatement { */ Sql setParam(String name, Object value); + /** + * 批量设置params + * @param params 参数集合 + * @return 原Sql对象,用于链式调用 + * @see #params() + */ + Sql setParams(Map params); + /** * 手动为某个语句参数设置适配器。 *

@@ -138,4 +156,6 @@ public interface Sql extends DaoStatement { * @return 当前SQL对象 */ Sql changePlaceholder(char param, char var); + + Sql appendSourceSql(String ext); } diff --git a/src/org/nutz/dao/sql/SqlCallback.java b/src/org/nutz/dao/sql/SqlCallback.java index 33b308ea09..7589cb7ce1 100644 --- a/src/org/nutz/dao/sql/SqlCallback.java +++ b/src/org/nutz/dao/sql/SqlCallback.java @@ -1,7 +1,6 @@ package org.nutz.dao.sql; import java.sql.Connection; - import java.sql.ResultSet; import java.sql.SQLException; diff --git a/src/org/nutz/dao/sql/SqlContext.java b/src/org/nutz/dao/sql/SqlContext.java index 3c46e1c5e5..2cc3bfcf8e 100644 --- a/src/org/nutz/dao/sql/SqlContext.java +++ b/src/org/nutz/dao/sql/SqlContext.java @@ -26,6 +26,8 @@ public class SqlContext { private Pager pager; private Map attrs; + + private int queryTimeout; public SqlContext() { // zozoh: 默认的,SQL 的游标类型是 TYPE_FORWARD_ONLY,即,使用各个数据库自有的分页语句 @@ -113,9 +115,14 @@ public Pager getPager() { public void setPager(Pager pager) { this.pager = pager; - // TODO 为何要这样写??为什么?!! SQLite死活不给我全部数据!! by wendal - // if (null != pager && pager.getPageSize() > 0) - // this.fetchSize = pager.getPageSize(); + } + + public int getQueryTimeout() { + return queryTimeout; + } + + public void setQueryTimeout(int queryTimeout) { + this.queryTimeout = queryTimeout; } } diff --git a/src/org/nutz/dao/util/Daos.java b/src/org/nutz/dao/util/Daos.java index b10c12656b..2ce08a42aa 100644 --- a/src/org/nutz/dao/util/Daos.java +++ b/src/org/nutz/dao/util/Daos.java @@ -13,7 +13,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; -import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -42,7 +41,9 @@ import org.nutz.dao.pager.Pager; import org.nutz.dao.sql.Sql; import org.nutz.dao.sql.SqlCallback; +import org.nutz.dao.util.tables.TablesFilter; import org.nutz.lang.Lang; +import org.nutz.lang.Mirror; import org.nutz.lang.Strings; import org.nutz.lang.random.R; import org.nutz.lang.util.Callback2; @@ -59,6 +60,7 @@ * @author wendal(wendal1985@gmail.com) * @author cqyunqin * @author rekoe(koukou890@qq.com) + * @author 黄川(huchuc@vip.qq.com) */ public abstract class Daos { @@ -84,11 +86,12 @@ public static void safeClose(Statement stat, ResultSet rs) { * Statement实例,可以为null */ public static void safeClose(Statement stat) { - if (null != stat) + if (null != stat) { try { stat.close(); } catch (Throwable e) {} + } } /** @@ -98,11 +101,12 @@ public static void safeClose(Statement stat) { * ResultSet实例,可以为null */ public static void safeClose(ResultSet rs) { - if (null != rs) + if (null != rs) { try { rs.close(); } catch (Throwable e) {} + } } /** @@ -117,12 +121,15 @@ public static void safeClose(ResultSet rs) { * 指定的colName找不到 */ public static int getColumnIndex(ResultSetMetaData meta, String colName) throws SQLException { - if (meta == null) + if (meta == null) { return 0; + } int columnCount = meta.getColumnCount(); - for (int i = 1; i <= columnCount; i++) - if (meta.getColumnName(i).equalsIgnoreCase(colName)) + for (int i = 1; i <= columnCount; i++) { + if (meta.getColumnName(i).equalsIgnoreCase(colName)) { return i; + } + } // TODO 尝试一下meta.getColumnLabel? log.debugf("Can not find @Column(%s) in table/view (%s)", colName, meta.getTableName(1)); throw Lang.makeThrow(SQLException.class, "Can not find @Column(%s)", colName); @@ -141,12 +148,13 @@ public static int getColumnIndex(ResultSetMetaData meta, String colName) throws */ public static boolean isIntLikeColumn(ResultSetMetaData meta, int index) throws SQLException { switch (meta.getColumnType(index)) { - case Types.BIGINT: - case Types.INTEGER: - case Types.SMALLINT: - case Types.TINYINT: - case Types.NUMERIC: - return true; + case Types.BIGINT: + case Types.INTEGER: + case Types.SMALLINT: + case Types.TINYINT: + case Types.NUMERIC: + return true; + default: } return false; } @@ -248,6 +256,7 @@ public static List queryWithLinks(final Dao dao, final Pager pager, final String regex) { Molecule> molecule = new Molecule>() { + @Override public void run() { List list = dao.query(classOfT, cnd, pager); dao.fetchLinks(list, regex); @@ -256,7 +265,7 @@ public void run() { }; return Trans.exec(molecule); } - + public static StringBuilder dataDict(DataSource ds, String... packages) { return dataDict(new NutDao(ds), packages); } @@ -271,8 +280,9 @@ public static StringBuilder dataDict(Dao dao, String... packages) { Iterator> it = ks.iterator(); while (it.hasNext()) { Class klass = it.next(); - if (klass.getAnnotation(Table.class) == null) + if (Mirror.me(klass).getAnnotation(Table.class) == null) { it.remove(); + } } // log.infof("Found %d table class", ks.size()); @@ -286,8 +296,9 @@ public static StringBuilder dataDict(Dao dao, String... packages) { sb.append(line); entity = dao.getEntity(klass); sb.append("表名 ").append(entity.getTableName()).append("\n\n"); - if (!Strings.isBlank(entity.getTableComment())) + if (!Strings.isBlank(entity.getTableComment())) { sb.append("表注释: ").append(entity.getTableComment()); + } sb.append("\t").append("Java类名 ").append(klass.getName()).append("\n\n"); sb.append("\t||序号||列名||数据类型||主键||非空||默认值||java属性名||java类型||注释||\n"); int index = 1; @@ -339,17 +350,18 @@ public static List query(Dao dao, @Deprecated public static long queryCount(Dao dao, String sql) { String tmpTable = "as _nutz_tmp"; - if (dao.meta().isDB2()) + if (dao.meta().isDB2()) { tmpTable = "as nutz_tmp_" + R.UU32(); - else if (dao.meta().isOracle()) + } else if (dao.meta().isOracle()) { tmpTable = ""; - else + } else { tmpTable += "_" + R.UU32(); + } Sql sql2 = Sqls.fetchLong("select count(1) from (" + sql + ")" + tmpTable); dao.execute(sql2); return sql2.getLong(); } - + /** * 查询某sql的结果条数 * @param dao 用于执行该count方法的dao实例 @@ -357,13 +369,15 @@ else if (dao.meta().isOracle()) */ public static long queryCount(Dao dao, Sql sql) { String tmpTable = "as _nutz_tmp"; - if (dao.meta().isDB2()) + if (dao.meta().isDB2()) { tmpTable = "as nutz_tmp_" + R.UU32(); - else if (dao.meta().isOracle()) + } else if (dao.meta().isOracle()) { tmpTable = ""; - else + } else { tmpTable += "_" + R.UU32(); + } Sql sql2 = Sqls.fetchLong("select count(1) from (" + sql.getSourceSql() + ")" + tmpTable); + sql2.setEntity(sql.getEntity()); for (String key : sql.params().keys()) { sql2.setParam(key, sql.params().get(key)); } @@ -380,10 +394,12 @@ else if (dao.meta().isOracle()) */ @SuppressWarnings({"rawtypes"}) public static void insertBySpecialChain(Dao dao, Entity en, String tableName, Chain chain) { - if (en != null) + if (en != null) { tableName = en.getTableName(); - if (tableName == null) + } + if (tableName == null) { throw Lang.makeThrow(DaoException.class, "tableName and en is NULL !!"); + } final StringBuilder sql = new StringBuilder("INSERT INTO ").append(tableName).append(" ("); StringBuilder _value_places = new StringBuilder(" VALUES("); final List values = new ArrayList(); @@ -394,21 +410,28 @@ public static void insertBySpecialChain(Dao dao, Entity en, String tableName, Ch MappingField mf = null; if (en != null) { mf = en.getField(colName); - if (mf != null) + if (mf != null) { colName = mf.getColumnNameInSql(); + } } sql.append(colName); if (head.special()) { _value_places.append(head.value()); } else { - if (en != null) + if (en != null) { mf = en.getField(head.name()); + } _value_places.append("?"); values.add(head.value()); - ValueAdaptor adaptor = Jdbcs.getAdaptorBy(head.value()); - if (mf != null && mf.getAdaptor() != null) - adaptor = mf.getAdaptor(); + ValueAdaptor adaptor = head.adaptor(); + if (adaptor == null) { + if (mf != null && mf.getAdaptor() != null) { + adaptor = mf.getAdaptor(); + } else { + adaptor = Jdbcs.getAdaptorBy(head.value()); + } + } adaptors.add(adaptor); } @@ -421,14 +444,17 @@ public static void insertBySpecialChain(Dao dao, Entity en, String tableName, Ch sql.append(")"); _value_places.append(")"); sql.append(_value_places); - if (log.isDebugEnabled()) + if (log.isDebugEnabled()) { log.debug(sql); + } dao.run(new ConnCallback() { + @Override public void invoke(Connection conn) throws Exception { PreparedStatement ps = conn.prepareStatement(sql.toString()); try { - for (int i = 0; i < values.size(); i++) + for (int i = 0; i < values.size(); i++) { adaptors.get(i).set(ps, values.get(i), i + 1); + } ps.execute(); } finally { @@ -452,21 +478,11 @@ public void invoke(Connection conn) throws Exception { public static void createTablesInPackage(final Dao dao, String packageName, boolean force) { List> list = new ArrayList>(); for(Class klass: Scans.me().scanPackage(packageName)) { - if (klass.getAnnotation(Table.class) != null) + if (Mirror.me(klass).getAnnotation(Table.class) != null) { list.add(klass); - }; - Collections.sort(list, new Comparator>() { - public int compare(Class prev, Class next) { - int links_prev = dao.getEntity(prev).getLinkFields(null).size(); - int links_next = dao.getEntity(next).getLinkFields(null).size(); - if (links_prev == links_next) - return 0; - return links_prev > links_next ? 1 : -1; } - - }); - for (Class klass : list) - dao.create(klass, force); + }; + createTables(dao,list,force); } /** @@ -483,6 +499,87 @@ public static void createTablesInPackage(Dao dao, Class oneClzInPackage, bool createTablesInPackage(dao, oneClzInPackage.getPackage().getName(), force); } + /** + * 为特定package下带@Table注解的类调用dao.create(XXX.class, force), + * 批量建表,优先建立带@ManyMany的表 + * + * @param dao + * Dao实例 + * @param oneClzInPackage + * 使用package中某一个class文件, 可以防止写错pkgName + * @param force + * 如果表存在,是否先删后建 + * @param filter + * 定义过滤器排除不需要自动创建的表 + */ + public static void createTablesInPackage(final Dao dao, Class oneClzInPackage, boolean force,TablesFilter filter) { + createTablesInPackage(dao, oneClzInPackage.getPackage().getName(), force,filter); + } + /** + * 为特定package下带@Table注解的类调用dao.create(XXX.class, force), + * 批量建表,优先建立带@ManyMany的表 + * + * @param dao + * Dao实例 + * @param packageName + * package名称,自动包含子类 + * @param force + * 如果表存在,是否先删后建 + * @param filter + * 定义过滤器排除不需要自动创建的表 + */ + public static void createTablesInPackage(final Dao dao, String packageName, boolean force,TablesFilter filter) { + List> list = new ArrayList>(); + for(Class klass: Scans.me().scanPackage(packageName)) { + Table table = Mirror.me(klass).getAnnotation(Table.class); + if (table != null && filter.match(klass,table)) { + list.add(klass); + } + } + createTables(dao,list,force); + } + + /** + * + * 批量建表,优先建立带@ManyMany的表 + * + * @param dao + * Dao实例 + * @param list + * 需要自动创建的表 + * @param force + * 如果表存在,是否先删后建 + */ + private static void createTables(final Dao dao, List> list, boolean force){ + Collections.sort(list, new Comparator>() { + @Override + public int compare(Class prev, Class next) { + int links_prev = dao.getEntity(prev).getLinkFields(null).size(); + int links_next = dao.getEntity(next).getLinkFields(null).size(); + if (links_prev == links_next) { + return 0; + } + return links_prev > links_next ? 1 : -1; + } + + }); + ArrayList es = new ArrayList(); + for (Class klass : list) { + try { + dao.create(klass, force); + } + catch (Exception e) { + es.add(new RuntimeException("class=" + klass.getName(), e)); + } + } + if (es.size() > 0) { + for (Exception exception : es) { + log.debug(exception.getMessage(), exception); + } + throw (RuntimeException)es.get(0); + } + } + private static Class[] iz = new Class[]{Dao.class}; /** @@ -523,8 +620,9 @@ public static Dao ext(Dao dao, Object tableName) { * @return 封装好的Dao实例 */ public static Dao ext(Dao dao, FieldFilter filter, Object tableName) { - if (tableName == null && filter == null) + if (tableName == null && filter == null) { return dao; + } ExtDaoInvocationHandler handler = new ExtDaoInvocationHandler(dao, filter, tableName); return (Dao) Proxy.newProxyInstance(dao.getClass().getClassLoader(), iz, handler); } @@ -534,8 +632,9 @@ public static boolean filterFields(Object obj, FieldMatcher matcher, Dao dao, Callback2 callback) { - if (obj == null) + if (obj == null) { return false; + } obj = Lang.first(obj); if (obj == null) { return false; @@ -556,8 +655,9 @@ public static boolean filterFields(Object obj, Iterator it = mfs.iterator(); while (it.hasNext()) { MappingField mf = it.next(); - if (!matcher.match(mf.getName())) + if (!matcher.match(mf.getName())) { it.remove(); + } } } boolean flag = false; @@ -568,32 +668,10 @@ public static boolean filterFields(Object obj, flag = true; continue; } - if (matcher.isIgnoreId() && mf.isId()) + if (!matcher.match(mf, obj)) { continue; - if (matcher.isIgnoreName() && mf.isName()) - continue; - if (matcher.isIgnorePk() && mf.isCompositePk()) - continue; - Object val = mf.getValue(obj); - if (val == null) { - if (matcher.isIgnoreNull()) - continue; - } else { - if (matcher.isIgnoreZero() - && val instanceof Number - && ((Number) val).doubleValue() == 0.0) { - continue; - } - if (matcher.isIgnoreDate() && val instanceof Date) { - continue; - } - if (matcher.isIgnoreBlankStr() - && val instanceof CharSequence - && Strings.isBlank((CharSequence) val)) { - continue; - } } - callback.invoke(mf, val); + callback.invoke(mf, mf.getValue(obj)); flag = true; } return flag; @@ -684,16 +762,22 @@ public static void migration(Dao dao, final boolean del, final boolean checkIndex, final Object tableName) { + migration(dao, dao.getEntity(klass), add, del, checkIndex, tableName); + } + public static void migration(Dao dao, + final Entity en, + final boolean add, + final boolean del, + final boolean checkIndex, + final Object tableName) { final JdbcExpert expert = dao.getJdbcExpert(); if (tableName != null && Strings.isNotBlank(tableName.toString())) { dao = ext(dao, tableName); } - final Entity en = dao.getEntity(klass); - if (!dao.exists(klass)) - return; final List sqls = new ArrayList(); final Set _indexs = new HashSet(); dao.run(new ConnCallback() { + @Override public void invoke(Connection conn) throws Exception { expert.setupEntityField(conn, en); Statement stat = null; @@ -711,8 +795,9 @@ public void invoke(Connection conn) throws Exception { columnNames.add(meta.getColumnName(i).toLowerCase()); } for (MappingField mf : en.getMappingFields()) { - if (mf.isReadonly()) + if (mf.isReadonly()) { continue; + } String colName = mf.getColumnName(); if (columnNames.contains(colName.toLowerCase())) { columnNames.remove(colName.toLowerCase()); @@ -735,12 +820,14 @@ public void invoke(Connection conn) throws Exception { } } // show index from mytable; - if (checkIndex) + if (checkIndex) { _indexs.addAll(expert.getIndexNames(en, conn)); + } } catch (SQLException e) { - if (log.isDebugEnabled()) + if (log.isDebugEnabled()) { log.debugf("migration Table '%s' fail!", en.getTableName(), e); + } } // Close ResultSet and Statement finally { @@ -822,8 +909,9 @@ private static UpdateIndexSql createIndexs(Dao dao, } MappingField mf = en.getColumn(indexName); if (mf != null) { - if (mf.isName()) + if (mf.isName()) { continue; + } } if (dao.meta().isSqlServer()) { delSqls.add(Sqls.createf("DROP INDEX %s.%s", @@ -862,7 +950,7 @@ public static void migration(Dao dao, boolean checkIndex, Object nameTable) { for (Class klass : Scans.me().scanPackage(packageName)) { - if (klass.getAnnotation(Table.class) != null) { + if (Mirror.me(klass).getAnnotation(Table.class) != null) { migration(dao, klass, add, del, checkIndex, nameTable); } } @@ -910,7 +998,7 @@ public static void migration(Dao dao, boolean del, boolean checkIndex) { for (Class klass : Scans.me().scanPackage(packageName)) { - if (klass.getAnnotation(Table.class) != null) { + if (Mirror.me(klass).getAnnotation(Table.class) != null) { migration(dao, klass, add, del, checkIndex, null); } } @@ -946,6 +1034,7 @@ public static void checkTableColumn(Dao dao, Object tableName, final Class cl final NutDao d = (NutDao) dao; final JdbcExpert expert = d.getJdbcExpert(); ext(d, tableName).run(new ConnCallback() { + @Override public void invoke(Connection conn) throws Exception { Entity en = d.getEntity(clsType); expert.setupEntityField(conn, en); @@ -978,10 +1067,12 @@ public static String getTableName(Dao dao, Class klass, Object target) { * 参考对象 */ public static String getTableName(Dao dao, final Entity en, Object target) { - if (target == null) + if (target == null) { return en.getTableName(); + } final String[] name = new String[1]; TableName.run(target, new Runnable() { + @Override public void run() { name[0] = en.getTableName(); } @@ -1027,12 +1118,12 @@ public static Set sql2003Keywords() { /** 是否把字段名给变成大写 */ public static boolean FORCE_UPPER_COLUMN_NAME = false; - + public static boolean FORCE_HUMP_COLUMN_NAME = false; /** varchar 字段的默认字段长度 */ public static int DEFAULT_VARCHAR_WIDTH = 128; - + /** Table&View名称生成器 */ public static interface NameMaker { String make(Class klass); @@ -1081,9 +1172,11 @@ protected ExtDaoInvocationHandler(Dao dao, FieldFilter filter, Object tableName) public FieldFilter filter; public Object tableName; + @Override public Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable { final Molecule m = new Molecule() { + @Override public void run() { try { setObj(method.invoke(dao, args)); @@ -1095,16 +1188,18 @@ public void run() { }; if (filter != null && tableName != null) { TableName.run(tableName, new Runnable() { + @Override public void run() { filter.run(m); } }); return m.getObj(); } - if (filter != null) + if (filter != null) { filter.run(m); - else + } else { TableName.run(tableName, m); + } return m.getObj(); } } diff --git a/src/org/nutz/dao/util/Pojos.java b/src/org/nutz/dao/util/Pojos.java index b45568f68b..2b28e6bc82 100644 --- a/src/org/nutz/dao/util/Pojos.java +++ b/src/org/nutz/dao/util/Pojos.java @@ -129,6 +129,12 @@ public static PItem cndAuto(Entity en, Object obj) { switch (en.getPkType()) { case ID: Number id = null != obj ? ((Number) en.getIdField().getValue(obj)) : null; + if (id == null && (en.getNameField() != null)) { + String name = (String) en.getNameField().getValue(obj); + if (!Strings.isBlank(name)) { + return cndName(en, name); + } + } return cndId(en, id); case NAME: String name = null != obj ? Strings.sNull(en.getNameField().getValue(obj), null) : null; @@ -146,7 +152,7 @@ public static PItem cndAuto(Entity en, Object obj) { if (Map.class.isAssignableFrom(en.getType())) { return null; // Map形式的话,不一定需要主键嘛 } - throw Lang.makeThrow("Don't know how to make fetch key %s:'%s'", en.getType() + throw Lang.makeThrow("Don't know how to make fetch key %s:'%s', need any of @Id/@Name/@Pk", en.getType() .getName(), obj); } } @@ -206,6 +212,7 @@ public static List getFieldsForInsert(Entity en, FieldMatcher f public static List getFieldsForUpdate(Entity en, FieldMatcher fm, Object refer) { List re = new ArrayList(en.getMappingFields().size()); + Object tmp = Lang.first(refer); for (MappingField mf : en.getMappingFields()) { if (mf.isPk()) { if (en.getPkType() == PkType.ID && mf.isId()) @@ -217,13 +224,17 @@ public static List getFieldsForUpdate(Entity en, FieldMatcher f } if (mf.isReadonly() || mf.isAutoIncreasement() || !mf.isUpdate()) continue; - else if (null != fm - && null != refer - && fm.isIgnoreNull() - && null == mf.getValue(Lang.first(refer))) - continue; - if (null == fm || fm.match(mf.getName())) - re.add(mf); + if (fm == null) { + re.add(mf); + } + else if (tmp == null) { + if (fm.match(mf.getName())) + re.add(mf); + } + else { + if (fm.match(mf, tmp)) + re.add(mf); + } } if (re.isEmpty() && log.isDebugEnabled()) log.debug("none field for update!"); diff --git a/src/org/nutz/dao/util/blob/SimpleBlob.java b/src/org/nutz/dao/util/blob/SimpleBlob.java index 3946c75148..95ffc0edb8 100644 --- a/src/org/nutz/dao/util/blob/SimpleBlob.java +++ b/src/org/nutz/dao/util/blob/SimpleBlob.java @@ -2,7 +2,6 @@ import java.io.File; import java.io.FileInputStream; -import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Serializable; @@ -39,11 +38,9 @@ public long length() throws SQLException { } public byte[] getBytes(long pos, int length) throws SQLException { - if (pos == 1 && length == length()) - try { - return Streams.readBytes(getBinaryStream()); - } catch (IOException e) { - } + if (pos == 1 && length == length()) { + return Streams.readBytesAndClose(getBinaryStream()); + } throw Lang.noImplement(); } diff --git a/src/org/nutz/dao/util/cri/AbstractSqlExpression.java b/src/org/nutz/dao/util/cri/AbstractSqlExpression.java index 0a372e119d..7a5c966ff0 100644 --- a/src/org/nutz/dao/util/cri/AbstractSqlExpression.java +++ b/src/org/nutz/dao/util/cri/AbstractSqlExpression.java @@ -3,6 +3,8 @@ import org.nutz.dao.entity.Entity; import org.nutz.dao.entity.MappingField; import org.nutz.dao.impl.sql.pojo.AbstractPItem; +import org.nutz.dao.util.lambda.LambdaQuery; +import org.nutz.dao.util.lambda.PFun; public abstract class AbstractSqlExpression extends AbstractPItem implements SqlExpression { @@ -10,12 +12,19 @@ public abstract class AbstractSqlExpression extends AbstractPItem implements Sql protected boolean not; - private String name; + protected String name; + + protected AbstractSqlExpression() { + } protected AbstractSqlExpression(String name) { this.name = name; } + protected AbstractSqlExpression(PFun name) { + this.name = LambdaQuery.resolve(name); + } + AbstractSqlExpression not() { this.not = true; return this; diff --git a/src/org/nutz/dao/util/cri/BetweenExpression.java b/src/org/nutz/dao/util/cri/BetweenExpression.java index 00f42e0956..4f83b7f7a6 100644 --- a/src/org/nutz/dao/util/cri/BetweenExpression.java +++ b/src/org/nutz/dao/util/cri/BetweenExpression.java @@ -4,6 +4,7 @@ import org.nutz.dao.entity.MappingField; import org.nutz.dao.jdbc.Jdbcs; import org.nutz.dao.jdbc.ValueAdaptor; +import org.nutz.dao.util.lambda.PFun; /** * between ? and ? @@ -16,13 +17,19 @@ public class BetweenExpression extends AbstractSqlExpression { private Object min; private Object max; - + public BetweenExpression(String name, Object min, Object max) { super(name); this.min = min; this.max = max; } + public BetweenExpression(PFun name, Object min, Object max) { + super(name); + this.min = min; + this.max = max; + } + public void joinSql(Entity en, StringBuilder sb) { if (not) sb.append(" NOT "); diff --git a/src/org/nutz/dao/util/cri/Exps.java b/src/org/nutz/dao/util/cri/Exps.java index 66145ca639..b13a23608c 100644 --- a/src/org/nutz/dao/util/cri/Exps.java +++ b/src/org/nutz/dao/util/cri/Exps.java @@ -3,13 +3,15 @@ import java.util.Collection; import org.nutz.castor.Castors; +import org.nutz.dao.util.lambda.LambdaQuery; +import org.nutz.dao.util.lambda.PFun; import org.nutz.lang.Lang; import org.nutz.lang.Mirror; import org.nutz.lang.Strings; /** * 表达式的帮助函数 - * + * * @author zozoh(zozohtnt@gmail.com) */ public abstract class Exps { @@ -21,59 +23,135 @@ public static SqlExpressionGroup begin() { public static Like like(String name, String value) { return Like.create(name, value, true); } - + + public static Like like(PFun name, String value) { + return Like.create(name, value, true); + } + public static Like like(String name, String value, boolean ignoreCase) { return Like.create(name, value, ignoreCase); } + public static Like like(PFun name, String value, boolean ignoreCase) { + return Like.create(name, value, ignoreCase); + } + public static IsNull isNull(String name) { return new IsNull(name); } + public static IsNull isNull(PFun name) { + return new IsNull(name); + } + public static SimpleExpression eq(String name, Object val) { return new SimpleExpression(name, "=", val); } - + + public static SimpleExpression eq(PFun name, Object val) { + return new SimpleExpression(name, "=", val); + } + public static SimpleExpression gt(String name, long val) { return new SimpleExpression(name, ">", val); } - + + public static SimpleExpression gt(PFun name, long val) { + return new SimpleExpression(name, ">", val); + } + public static SimpleExpression lt(String name, long val) { return new SimpleExpression(name, "<", val); } - + + public static SimpleExpression lt(PFun name, long val) { + return new SimpleExpression(name, "<", val); + } + public static SimpleExpression gte(String name, long val) { return new SimpleExpression(name, ">=", val); } - + + public static SimpleExpression gte(PFun name, long val) { + return new SimpleExpression(name, ">=", val); + } + public static SimpleExpression lte(String name, long val) { return new SimpleExpression(name, "<=", val); } - + + public static SimpleExpression lte(PFun name, long val) { + return new SimpleExpression(name, "<=", val); + } + public static IntRange inInt(String name, int... ids) { return new IntRange(name, ids); } + public static IntRange inInt(PFun name, int... ids) { + return new IntRange(LambdaQuery.resolve(name), ids); + } + + public static IntRange inInt(String name, Integer[] ids) { + return new IntRange(name, ids); + } + + public static IntRange inInt(PFun name, Integer[] ids) { + return new IntRange(LambdaQuery.resolve(name), ids); + } + public static LongRange inLong(String name, long... ids) { return new LongRange(name, ids); } + public static LongRange inLong(PFun name, long... ids) { + return new LongRange(LambdaQuery.resolve(name), ids); + } + + public static LongRange inLong(String name, Long[] ids) { + return new LongRange(name, ids); + } + + public static LongRange inLong(PFun name, Long[] ids) { + return new LongRange(LambdaQuery.resolve(name), ids); + } + public static NameRange inStr(String name, String... names) { return new NameRange(name, names); } + public static NameRange inStr(PFun name, String... names) { + return new NameRange(name, names); + } + public static SqlRange inSql(String name, String subSql, Object... args) { return new SqlRange(name, subSql, args); } + public static SqlRange inSql(PFun name, String subSql, Object... args) { + return new SqlRange(name, subSql, args); + } + public static SqlValueRange inSql2(String name, String subSql, Object... values) { return new SqlValueRange(name, subSql, values); } + public static SqlValueRange inSql2(PFun name, String subSql, Object... values) { + return new SqlValueRange(name, subSql, values); + } + public static SqlValueRange inSql2(String name, String subSql, Collection collection) { return new SqlValueRange(name, subSql, collection.toArray()); } + public static SqlValueRange inSql2(PFun name, String subSql, Collection collection) { + return new SqlValueRange(name, subSql, collection.toArray()); + } + + public static SqlExpression create(PFun name, String op, Object value) { + return create(LambdaQuery.resolve(name), op, value); + } + public static SqlExpression create(String name, String op, Object value) { op = Strings.trim(op.toUpperCase()); @@ -95,7 +173,7 @@ else if ("IN".equals(op) || "NOT IN".equals(op)) { Class type = value.getClass(); SqlExpression re; int len = Lang.eleSize(value); - if (len < 1) { // 如果空数组/空集合,则返回 @sinec 1.r.57 + if (len < 1) { // 如果空数组/空集合,则返回 @since 1.r.57 re = new Static("1 != 1"); } // 数组 diff --git a/src/org/nutz/dao/util/cri/GroupBySet.java b/src/org/nutz/dao/util/cri/GroupBySet.java index 4bc348905f..300b3b8528 100644 --- a/src/org/nutz/dao/util/cri/GroupBySet.java +++ b/src/org/nutz/dao/util/cri/GroupBySet.java @@ -3,26 +3,33 @@ import org.nutz.dao.Condition; import org.nutz.dao.entity.Entity; import org.nutz.dao.sql.GroupBy; +import org.nutz.dao.util.lambda.LambdaQuery; +import org.nutz.dao.util.lambda.PFun; + public class GroupBySet extends OrderBySet implements GroupBy { private static final long serialVersionUID = 1L; private String[] names; - + private Condition having; - + public GroupBySet() {} - + public GroupBySet(String...names) { this.names = names; } - + + public GroupBySet(PFun... names) { + this.names = LambdaQuery.resolves(names); + } + public GroupBy having(Condition cnd) { having = cnd; return this; } - + public void joinSql(Entity en, StringBuilder sb) { if (names == null || names.length == 0) return; @@ -45,9 +52,14 @@ public void joinSql(Entity en, StringBuilder sb) { } } } - + public GroupBy groupBy(String ... names) { this.names = names; return this; } + + public GroupBy groupBy(PFun... names) { + this.names = LambdaQuery.resolves(names); + return this; + } } diff --git a/src/org/nutz/dao/util/cri/IntRange.java b/src/org/nutz/dao/util/cri/IntRange.java index 23fb21511f..1b08cc3865 100644 --- a/src/org/nutz/dao/util/cri/IntRange.java +++ b/src/org/nutz/dao/util/cri/IntRange.java @@ -2,14 +2,30 @@ public class IntRange extends NumberRange { - private static final long serialVersionUID = 1L; - - IntRange(String name, int... ids) { - super(name); - this.not = false; - this.ids = new long[ids.length]; - for (int i = 0; i < ids.length; i++) - this.ids[i] = ids[i]; - } - + private static final long serialVersionUID = 1L; + + public IntRange(String name, int... ids) { + super(name); + this.not = false; + if (ids != null) { + this.ids = new long[ids.length]; + for (int i = 0; i < ids.length; i++) + this.ids[i] = ids[i]; + } else { + this.ids = null; + } + } + + public IntRange(String name, Integer[] ids) { + super(name); + this.not = false; + if (ids != null) { + this.ids = new long[ids.length]; + for (int i = 0; i < ids.length; i++) + this.ids[i] = ids[i]; + } else { + this.ids = null; + } + } + } diff --git a/src/org/nutz/dao/util/cri/IsNull.java b/src/org/nutz/dao/util/cri/IsNull.java index e082f865f1..16c0e6151a 100644 --- a/src/org/nutz/dao/util/cri/IsNull.java +++ b/src/org/nutz/dao/util/cri/IsNull.java @@ -1,6 +1,7 @@ package org.nutz.dao.util.cri; import org.nutz.dao.entity.Entity; +import org.nutz.dao.util.lambda.PFun; public class IsNull extends NoParamsSqlExpression { @@ -11,6 +12,11 @@ public IsNull(String name) { this.not = false; } + public IsNull(PFun name) { + super(name); + this.not = false; + } + public void joinSql(Entity en, StringBuilder sb) { sb.append(_fmtcol(en)); sb.append(" IS "); diff --git a/src/org/nutz/dao/util/cri/Like.java b/src/org/nutz/dao/util/cri/Like.java index 9c11830b49..1782c732ef 100644 --- a/src/org/nutz/dao/util/cri/Like.java +++ b/src/org/nutz/dao/util/cri/Like.java @@ -3,6 +3,8 @@ import org.nutz.dao.entity.Entity; import org.nutz.dao.jdbc.Jdbcs; import org.nutz.dao.jdbc.ValueAdaptor; +import org.nutz.dao.util.lambda.LambdaQuery; +import org.nutz.dao.util.lambda.PFun; public class Like extends AbstractSqlExpression { @@ -17,6 +19,10 @@ static Like create(String name, String value, boolean ignoreCase) { return like; } + static Like create(PFun name, String value, boolean ignoreCase) { + return create(LambdaQuery.resolve(name),value,ignoreCase); + } + private String value; private boolean ignoreCase; @@ -29,6 +35,11 @@ private Like(String name) { super(name); } + private Like(PFun name) { + super(name); + } + + public void joinSql(Entity en, StringBuilder sb) { String colName = _fmtcol(en); if (not) diff --git a/src/org/nutz/dao/util/cri/LongRange.java b/src/org/nutz/dao/util/cri/LongRange.java index 12d2464532..4dd79b295a 100644 --- a/src/org/nutz/dao/util/cri/LongRange.java +++ b/src/org/nutz/dao/util/cri/LongRange.java @@ -9,5 +9,13 @@ public class LongRange extends NumberRange { this.ids = ids; this.not = false; } + + LongRange(String name, Long[] ids) { + super(name); + this.ids = new long[ids.length]; + for (int i = 0; i < ids.length; i++) + this.ids[i] = ids[i]; + this.not = false; + } } diff --git a/src/org/nutz/dao/util/cri/NameRange.java b/src/org/nutz/dao/util/cri/NameRange.java index a171e67206..e312317eb3 100644 --- a/src/org/nutz/dao/util/cri/NameRange.java +++ b/src/org/nutz/dao/util/cri/NameRange.java @@ -3,6 +3,7 @@ import org.nutz.dao.entity.Entity; import org.nutz.dao.jdbc.Jdbcs; import org.nutz.dao.jdbc.ValueAdaptor; +import org.nutz.dao.util.lambda.PFun; public class NameRange extends AbstractSqlExpression { @@ -16,6 +17,12 @@ public class NameRange extends AbstractSqlExpression { this.not = false; } + NameRange(PFun name, String... names) { + super(name); + this.names = names; + this.not = false; + } + public void joinSql(Entity en, StringBuilder sb) { if (names.length > 0) { sb.append(_fmtcol(en)); diff --git a/src/org/nutz/dao/util/cri/NestExps.java b/src/org/nutz/dao/util/cri/NestExps.java index f19a57209a..e840889a52 100644 --- a/src/org/nutz/dao/util/cri/NestExps.java +++ b/src/org/nutz/dao/util/cri/NestExps.java @@ -1,6 +1,8 @@ package org.nutz.dao.util.cri; import org.nutz.dao.Nesting; +import org.nutz.dao.util.lambda.LambdaQuery; +import org.nutz.dao.util.lambda.PFun; import org.nutz.lang.Lang; import org.nutz.lang.Strings; @@ -8,30 +10,55 @@ * 逻辑基本与{@link Exps}类似,当传入Cnd.where()最后一个条件传入Nesting参数时会调用此类方法来构造sql条件. */ public class NestExps { + public static NestingExpression eq(String name, Nesting val) { return new NestingExpression(name, "=", val); } + public static NestingExpression eq(PFun name, Nesting val) { + return new NestingExpression(name, "=", val); + } + public static NestingExpression notEq(String name, Nesting val) { return new NestingExpression(name, "<>", val); } + public static NestingExpression notEq(PFun name, Nesting val) { + return new NestingExpression(name, "<>", val); + } + public static NestingExpression like(String name, Nesting value) { return new NestingExpression(name, "LIKE", value); } + public static NestingExpression like(PFun name, Nesting value) { + return new NestingExpression(name, "LIKE", value); + } + public static NestingExpression inSql(String name, Nesting value) { return new NestingExpression(name, "IN", value); } - public static NestingExpression exitsts(Nesting value) { - return new NestingExpression(null, "EXITSTS", value); + public static NestingExpression inSql(PFun name, Nesting value) { + return new NestingExpression(name, "IN", value); + } + + public static NestingExpression exists(Nesting value) { + return new NestingExpression("EXISTS", value); } public static NestingExpression otherSymbol(String name, String op, Nesting value) { return new NestingExpression(name, op, value); } + public static NestingExpression otherSymbol(PFun name, String op, Nesting value) { + return new NestingExpression(name, op, value); + } + + public static SqlExpression create(PFun name, String op, Nesting value) { + return create(LambdaQuery.resolve(name),op,value); + } + public static SqlExpression create(String name, String op, Nesting value) { op = Strings.trim(op.toUpperCase()); if (value == null) { @@ -44,9 +71,9 @@ public static SqlExpression create(String name, String op, Nesting value) { return notEq(name, value); } else if ("IN".equals(op) || "NOT IN".equals(op)) { return inSql(name, value).setNot(op.startsWith("NOT")); - } else if ("EXITSTS".equals(op) || "NOT EXITSTS".equals(op)) { + } else if ("EXISTS".equals(op) || "NOT EXISTS".equals(op)) { // TODO op为EXITSTS的情况下,name!=null or name.length!=0 是否需要报错? - return exitsts(value).setNot(op.startsWith("NOT")); + return exists(value).setNot(op.startsWith("NOT")); } return otherSymbol(name, op, value); } diff --git a/src/org/nutz/dao/util/cri/NestingExpression.java b/src/org/nutz/dao/util/cri/NestingExpression.java index b9278eacab..767c27ac28 100644 --- a/src/org/nutz/dao/util/cri/NestingExpression.java +++ b/src/org/nutz/dao/util/cri/NestingExpression.java @@ -3,6 +3,7 @@ import org.nutz.dao.Nesting; import org.nutz.dao.entity.Entity; import org.nutz.dao.jdbc.ValueAdaptor; +import org.nutz.dao.util.lambda.PFun; /** * 与{@linkplain SimpleExpression}类似,但是 @@ -15,12 +16,24 @@ public class NestingExpression extends AbstractSqlExpression { private String op; private Nesting value; + + public NestingExpression(String op, Nesting value) { + this.op = op; + this.value = value; + } + public NestingExpression(String name, String op, Nesting value) { super(name); this.op = op; this.value = value; } + public NestingExpression(PFun name, String op, Nesting value) { + super(name); + this.op = op; + this.value = value; + } + public void joinSql(Entity en, StringBuilder sb) { if (!"EXISTS".equals(op)) sb.append(_fmtcol(en)); diff --git a/src/org/nutz/dao/util/cri/NoParamsSqlExpression.java b/src/org/nutz/dao/util/cri/NoParamsSqlExpression.java index a3c7b8bafb..f08a58ff40 100644 --- a/src/org/nutz/dao/util/cri/NoParamsSqlExpression.java +++ b/src/org/nutz/dao/util/cri/NoParamsSqlExpression.java @@ -2,6 +2,7 @@ import org.nutz.dao.entity.Entity; import org.nutz.dao.jdbc.ValueAdaptor; +import org.nutz.dao.util.lambda.PFun; public abstract class NoParamsSqlExpression extends AbstractSqlExpression { @@ -11,6 +12,10 @@ protected NoParamsSqlExpression(String name) { super(name); } + protected NoParamsSqlExpression(PFun name) { + super(name); + } + public int joinAdaptor(Entity en, ValueAdaptor[] adaptors, int off) { return off; } diff --git a/src/org/nutz/dao/util/cri/NumberRange.java b/src/org/nutz/dao/util/cri/NumberRange.java index ab082856a5..caab88c2d0 100644 --- a/src/org/nutz/dao/util/cri/NumberRange.java +++ b/src/org/nutz/dao/util/cri/NumberRange.java @@ -3,6 +3,7 @@ import org.nutz.dao.entity.Entity; import org.nutz.dao.jdbc.Jdbcs; import org.nutz.dao.jdbc.ValueAdaptor; +import org.nutz.dao.util.lambda.PFun; public abstract class NumberRange extends AbstractSqlExpression { @@ -10,7 +11,11 @@ public abstract class NumberRange extends AbstractSqlExpression { protected long[] ids; - protected NumberRange(String name) { + public NumberRange(String name) { + super(name); + } + + public NumberRange(PFun name) { super(name); } @@ -23,7 +28,7 @@ public void joinSql(Entity en, StringBuilder sb) { for (int i = 0; i < ids.length; i++) sb.append("?,"); sb.setCharAt(sb.length() - 1, ')'); - } //OK,无需添加. + } // OK,无需添加. } public int joinAdaptor(Entity en, ValueAdaptor[] adaptors, int off) { diff --git a/src/org/nutz/dao/util/cri/OrderByItem.java b/src/org/nutz/dao/util/cri/OrderByItem.java index 1fdac12e1f..08b7861756 100644 --- a/src/org/nutz/dao/util/cri/OrderByItem.java +++ b/src/org/nutz/dao/util/cri/OrderByItem.java @@ -2,6 +2,8 @@ import org.nutz.dao.entity.Entity; import org.nutz.dao.impl.sql.pojo.NoParamsPItem; +import org.nutz.dao.util.lambda.LambdaQuery; +import org.nutz.dao.util.lambda.PFun; public class OrderByItem extends NoParamsPItem { diff --git a/src/org/nutz/dao/util/cri/OrderBySet.java b/src/org/nutz/dao/util/cri/OrderBySet.java index f8d029eefb..a5fbaad9fd 100644 --- a/src/org/nutz/dao/util/cri/OrderBySet.java +++ b/src/org/nutz/dao/util/cri/OrderBySet.java @@ -7,6 +7,8 @@ import org.nutz.dao.impl.sql.pojo.NoParamsPItem; import org.nutz.dao.sql.OrderBy; import org.nutz.dao.sql.Pojo; +import org.nutz.dao.util.lambda.LambdaQuery; +import org.nutz.dao.util.lambda.PFun; public class OrderBySet extends NoParamsPItem implements OrderBy { @@ -42,13 +44,23 @@ public OrderBy asc(String name) { return this; } + @Override + public OrderBy asc(PFun name) { + return asc(LambdaQuery.resolve(name)); + } + public OrderBy desc(String name) { OrderByItem desc = new OrderByItem(name, "DESC"); desc.setPojo(pojo); list.add(desc); return this; } - + + @Override + public OrderBy desc(PFun name) { + return desc(LambdaQuery.resolve(name)); + } + public void setPojo(Pojo pojo) { super.setPojo(pojo); for (OrderByItem obi : list) @@ -58,11 +70,11 @@ public void setPojo(Pojo pojo) { public List getItems() { return list; } - + public String toString() { return toSql(null); } - + public OrderBy orderBy(String name, String dir) { if ("asc".equalsIgnoreCase(dir)) { this.asc(name); @@ -71,4 +83,14 @@ public OrderBy orderBy(String name, String dir) { } return this; } + + @Override + public OrderBy orderBy(PFun name, String dir) { + if ("asc".equalsIgnoreCase(dir)) { + this.asc(name); + } else { + this.desc(name); + } + return this; + } } diff --git a/src/org/nutz/dao/util/cri/SimpleCriteria.java b/src/org/nutz/dao/util/cri/SimpleCriteria.java index b17d536513..69c89e28c9 100644 --- a/src/org/nutz/dao/util/cri/SimpleCriteria.java +++ b/src/org/nutz/dao/util/cri/SimpleCriteria.java @@ -10,6 +10,8 @@ import org.nutz.dao.sql.GroupBy; import org.nutz.dao.sql.OrderBy; import org.nutz.dao.sql.Pojo; +import org.nutz.dao.util.lambda.PFun; + public class SimpleCriteria extends AbstractPItem implements Criteria, OrderBy, GroupBy { @@ -18,27 +20,27 @@ public class SimpleCriteria extends AbstractPItem implements Criteria, OrderBy, private SqlExpressionGroup where; private OrderBySet orderBy; - + private GroupBySet groupBy; private Pager pager; - - private String anything; + + private String beforeWhere; public SimpleCriteria() { where = new SqlExpressionGroup(); orderBy = new OrderBySet(); groupBy = new GroupBySet(); } - - public SimpleCriteria(String anything) { + + public SimpleCriteria(String beforeWhere) { this(); - this.anything = anything; + this.beforeWhere = beforeWhere; } public void joinSql(Entity en, StringBuilder sb) { - if (anything != null) - sb.append(anything); + if (beforeWhere != null) + sb.append(beforeWhere); where.joinSql(en, sb); groupBy.joinSql(en, sb); orderBy.joinSql(en, sb); @@ -100,19 +102,35 @@ public OrderBy asc(String name) { return orderBy.asc(name); } + @Override + public OrderBy asc(PFun name) { + return orderBy.asc(name); + } + public OrderBy desc(String name) { return orderBy.desc(name); } + @Override + public OrderBy desc(PFun name) { + return orderBy.desc(name); + } + public SqlExpressionGroup where() { return where; } - + public GroupBy groupBy(String...names) { groupBy = new GroupBySet(names); return this; } - + + @Override + public GroupBy groupBy(PFun... names) { + groupBy = new GroupBySet(names); + return this; + } + public GroupBy having(Condition cnd) { groupBy.having(cnd); return this; @@ -125,7 +143,7 @@ public OrderBy getOrderBy() { public String toString() { return toSql(null); } - + public OrderBy orderBy(String name, String dir) { if ("asc".equalsIgnoreCase(dir)) { this.asc(name); @@ -134,8 +152,22 @@ public OrderBy orderBy(String name, String dir) { } return this; } - + + @Override + public OrderBy orderBy(PFun name, String dir) { + if ("asc".equalsIgnoreCase(dir)) { + this.asc(name); + } else { + this.desc(name); + } + return this; + } + public GroupBy getGroupBy() { return groupBy; } + + public String getBeforeWhere() { + return beforeWhere; + } } diff --git a/src/org/nutz/dao/util/cri/SimpleExpression.java b/src/org/nutz/dao/util/cri/SimpleExpression.java index ac1e66995a..4ccc2a9070 100644 --- a/src/org/nutz/dao/util/cri/SimpleExpression.java +++ b/src/org/nutz/dao/util/cri/SimpleExpression.java @@ -4,6 +4,7 @@ import org.nutz.dao.entity.MappingField; import org.nutz.dao.jdbc.Jdbcs; import org.nutz.dao.jdbc.ValueAdaptor; +import org.nutz.dao.util.lambda.PFun; public class SimpleExpression extends AbstractSqlExpression { @@ -18,6 +19,12 @@ public SimpleExpression(String name, String op, Object val) { this.value = val; } + public SimpleExpression(PFun name, String op, Object val) { + super(name); + this.op = op; + this.value = val; + } + public void joinSql(Entity en, StringBuilder sb) { if (not) sb.append(" NOT "); diff --git a/src/org/nutz/dao/util/cri/SqlExpressionGroup.java b/src/org/nutz/dao/util/cri/SqlExpressionGroup.java index cef5a65323..024d247ab6 100644 --- a/src/org/nutz/dao/util/cri/SqlExpressionGroup.java +++ b/src/org/nutz/dao/util/cri/SqlExpressionGroup.java @@ -10,6 +10,7 @@ import org.nutz.dao.impl.sql.pojo.AbstractPItem; import org.nutz.dao.jdbc.ValueAdaptor; import org.nutz.dao.sql.Pojo; +import org.nutz.dao.util.lambda.PFun; import org.nutz.log.Log; import org.nutz.log.Logs; @@ -21,9 +22,9 @@ public class SqlExpressionGroup extends AbstractPItem implements SqlExpression { private static final long serialVersionUID = 1L; private List exps; - + protected boolean not; - + private static final Log log = Logs.get(); public SqlExpressionGroup() { @@ -34,6 +35,10 @@ public SqlExpressionGroup and(String name, String op, Object value) { return and(Exps.create(name, op, value)); } + public SqlExpressionGroup and(PFun name, String op, Object value) { + return and(Exps.create(name, op, value)); + } + public SqlExpressionGroup and(SqlExpression exp) { if (exp == null) { if (log.isTraceEnabled()) @@ -51,10 +56,22 @@ public SqlExpressionGroup andEquals(String name, Object val) { return and(eq(name, val)); } + public SqlExpressionGroup andEquals(PFun name, Object val) { + if (null == val) + return andIsNull(name); + return and(eq(name, val)); + } + public SqlExpressionGroup andNotEquals(String name, Object val) { if (null == val) return andNotIsNull(name); return and(eq(name, val).not()); + } + + public SqlExpressionGroup andNotEquals(PFun name, Object val) { + if (null == val) + return andNotIsNull(name); + return and(eq(name, val).not()); } @@ -62,110 +79,320 @@ public SqlExpressionGroup andIsNull(String name) { return and(isNull(name)); } + public SqlExpressionGroup andIsNull(PFun name) { + return and(isNull(name)); + } + public SqlExpressionGroup andNotIsNull(String name) { return and(isNull(name).not()); } + public SqlExpressionGroup andNotIsNull(PFun name) { + return and(isNull(name).not()); + } + public SqlExpressionGroup andGT(String name, long val) { return and(gt(name, val)); } + public SqlExpressionGroup andGT(PFun name, long val) { + return and(gt(name, val)); + } + public SqlExpressionGroup andGTE(String name, long val) { return and(gte(name, val)); } + public SqlExpressionGroup andGTE(PFun name, long val) { + return and(gte(name, val)); + } + public SqlExpressionGroup andLT(String name, long val) { return and(lt(name, val)); } + public SqlExpressionGroup andLT(PFun name, long val) { + return and(lt(name, val)); + } + public SqlExpressionGroup andLTE(String name, long val) { return and(lte(name, val)); } + public SqlExpressionGroup andLTE(PFun name, long val) { + return and(lte(name, val)); + } + public SqlExpressionGroup andIn(String name, long... ids) { return and(inLong(name, ids)); } + public SqlExpressionGroup andIn(PFun name, long... ids) { + return and(inLong(name, ids)); + } + + public SqlExpressionGroup andInArray(String name, long[] ids) { + return and(inLong(name, ids)); + } + + public SqlExpressionGroup andInArray(PFun name, long[] ids) { + return and(inLong(name, ids)); + } + + public SqlExpressionGroup andInList(String name, List ids) { + return and(inLong(name, ids.toArray(new Long[ids.size()]))); + } + + public SqlExpressionGroup andInList(PFun name, List ids) { + return and(inLong(name, ids.toArray(new Long[ids.size()]))); + } + public SqlExpressionGroup andInIntArray(String name, int... ids) { return and(inInt(name, ids)); } + public SqlExpressionGroup andInIntArray(PFun name, int... ids) { + return and(inInt(name, ids)); + } + + public SqlExpressionGroup andInIntArray2(String name, int[] ids) { + return and(inInt(name, ids)); + } + public SqlExpressionGroup andInIntArray2(PFun name, int[] ids) { + return and(inInt(name, ids)); + } + + public SqlExpressionGroup andInIntList(String name, List ids) { + return and(inInt(name, ids.toArray(new Integer[ids.size()]))); + } + + public SqlExpressionGroup andInIntList(PFun name, List ids) { + return and(inInt(name, ids.toArray(new Integer[ids.size()]))); + } + public SqlExpressionGroup andIn(String name, String... names) { return and(inStr(name, names)); } + public SqlExpressionGroup andIn(PFun name, String... names) { + return and(inStr(name, names)); + } + + public SqlExpressionGroup andInStrArray(String name, String[] names) { + return and(inStr(name, names)); + } + + public SqlExpressionGroup andInStrArray(PFun name, String[] names) { + return and(inStr(name, names)); + } + + public SqlExpressionGroup andInStrList(String name, List names) { + return and(inStr(name, names.toArray(new String[names.size()]))); + } + + public SqlExpressionGroup andInStrList(PFun name, List names) { + return and(inStr(name, names.toArray(new String[names.size()]))); + } + + /** + * 用法 + * cnd.where().andInBySql("dept_id","SELECT id FROM sys_dept WHERE FIND_IN_SET ('%s',ancestors)", deptId); + * @param name 查询条件 + * @param subSql sql + * @param args 参数 + * @return + */ public SqlExpressionGroup andInBySql(String name, String subSql, Object... args) { return and(inSql(name, subSql, args)); } + public SqlExpressionGroup andInBySql(PFun name, String subSql, Object... args) { + return and(inSql(name, subSql, args)); + } + public SqlExpressionGroup andNotInBySql(String name, String subSql, Object... args) { return and(inSql(name, subSql, args).not()); } - + + public SqlExpressionGroup andNotInBySql(PFun name, String subSql, Object... args) { + return and(inSql(name, subSql, args).not()); + } + public SqlExpressionGroup andInBySql2(String name, String subSql, Object... values) { return and(inSql2(name, subSql, values)); } + public SqlExpressionGroup andInBySql2(PFun name, String subSql, Object... values) { + return and(inSql2(name, subSql, values)); + } + public SqlExpressionGroup andNotInBySql2(String name, String subSql, Object... values) { return and(inSql2(name, subSql, values).not()); } + public SqlExpressionGroup andNotInBySql2(PFun name, String subSql, Object... values) { + return and(inSql2(name, subSql, values).not()); + } + public SqlExpressionGroup andNotIn(String name, long... ids) { return and(inLong(name, ids).not()); } + public SqlExpressionGroup andNotIn(PFun name, long... ids) { + return and(inLong(name, ids).not()); + } + + public SqlExpressionGroup andNotInArray(String name, long[] ids) { + return and(inLong(name, ids).not()); + } + + public SqlExpressionGroup andNotInArray(PFun name, long[] ids) { + return and(inLong(name, ids).not()); + } + + public SqlExpressionGroup andNotInList(String name, List ids) { + return and(inLong(name, ids.toArray(new Long[ids.size()])).not()); + } + + public SqlExpressionGroup andNotInList(PFun name, List ids) { + return and(inLong(name, ids.toArray(new Long[ids.size()])).not()); + } + public SqlExpressionGroup andNotIn(String name, int... ids) { return and(inInt(name, ids).not()); } + public SqlExpressionGroup andNotIn(PFun name, int... ids) { + return and(inInt(name, ids).not()); + } + + public SqlExpressionGroup andNotInArray(String name, int[] ids) { + return and(inInt(name, ids).not()); + } + + public SqlExpressionGroup andNotInArray(PFun name, int[] ids) { + return and(inInt(name, ids).not()); + } + + public SqlExpressionGroup andNotInIntList(String name, List ids) { + return and(inInt(name, ids.toArray(new Integer[ids.size()])).not()); + } + + public SqlExpressionGroup andNotInIntList(PFun name, List ids) { + return and(inInt(name, ids.toArray(new Integer[ids.size()])).not()); + } + public SqlExpressionGroup andNotIn(String name, String... names) { return and(inStr(name, names).not()); } + public SqlExpressionGroup andNotIn(PFun name, String... names) { + return and(inStr(name, names).not()); + } + + public SqlExpressionGroup andNotInArray(String name, String[] names) { + return and(inStr(name, names).not()); + } + + public SqlExpressionGroup andNotInArray(PFun name, String[] names) { + return and(inStr(name, names).not()); + } + + public SqlExpressionGroup andNotInStrList(String name, List names) { + return and(inStr(name, names.toArray(new String[names.size()])).not()); + } + + public SqlExpressionGroup andNotInStrList(PFun name, List names) { + return and(inStr(name, names.toArray(new String[names.size()])).not()); + } + public SqlExpressionGroup andLike(String name, String value) { return and(like(name, value)); } - + + public SqlExpressionGroup andLike(PFun name, String value) { + return and(like(name, value)); + } + public SqlExpressionGroup andLikeL(String name, String value) { return and(like(name, value).left(null)); } - + + public SqlExpressionGroup andLikeL(PFun name, String value) { + return and(like(name, value).left(null)); + } + public SqlExpressionGroup andLikeR(String name, String value) { return and(like(name, value).right(null)); } + public SqlExpressionGroup andLikeR(PFun name, String value) { + return and(like(name, value).right(null)); + } + public SqlExpressionGroup andNotLike(String name, String value) { return and(like(name, value).not()); } + public SqlExpressionGroup andNotLike(PFun name, String value) { + return and(like(name, value).not()); + } public SqlExpressionGroup andNotLikeL(String name, String value) { return and(like(name, value).left(null).not()); } + public SqlExpressionGroup andNotLikeL(PFun name, String value) { + return and(like(name, value).left(null).not()); + } + public SqlExpressionGroup andNotLikeR(String name, String value) { return and(like(name, value).right(null).not()); } + public SqlExpressionGroup andNotLikeR(PFun name, String value) { + return and(like(name, value).right(null).not()); + } + public SqlExpressionGroup andLike(String name, String value, boolean ignoreCase) { return and(like(name, value, ignoreCase)); } + public SqlExpressionGroup andLike(PFun name, String value, boolean ignoreCase) { + return and(like(name, value, ignoreCase)); + } + public SqlExpressionGroup andNotLike(String name, String value, boolean ignoreCase) { return and(like(name, value, ignoreCase).not()); } - + + public SqlExpressionGroup andNotLike(PFun name, String value, boolean ignoreCase) { + return and(like(name, value, ignoreCase).not()); + } + public SqlExpressionGroup andLike(String name, String value, String left, String right, boolean ignoreCase) { return and(like(name, value, ignoreCase).left(left).right(right)); } - + + public SqlExpressionGroup andLike(PFun name, String value, String left, String right, boolean ignoreCase) { + return and(like(name, value, ignoreCase).left(left).right(right)); + } + public SqlExpressionGroup andNotLike(String name, String value, String left, String right, boolean ignoreCase) { return and(like(name, value, ignoreCase).left(left).right(right).not()); } + public SqlExpressionGroup andNotLike(PFun name, String value, String left, String right, boolean ignoreCase) { + return and(like(name, value, ignoreCase).left(left).right(right).not()); + } + public SqlExpressionGroup or(String name, String op, Object value) { return or(Exps.create(name, op, value)); } + public SqlExpressionGroup or(PFun name, String op, Object value) { + return or(Exps.create(name, op, value)); + } + public SqlExpressionGroup or(SqlExpression exp) { if (exp == null) { if (log.isTraceEnabled()) @@ -181,124 +408,239 @@ public SqlExpressionGroup orEquals(String name, Object val) { return or(eq(name, val)); } + public SqlExpressionGroup orEquals(PFun name, Object val) { + return or(eq(name, val)); + } + public SqlExpressionGroup orNotEquals(String name, Object val) { return or(eq(name, val).not()); + } + public SqlExpressionGroup orNotEquals(PFun name, Object val) { + return or(eq(name, val).not()); } public SqlExpressionGroup orIsNull(String name) { return or(isNull(name)); } + public SqlExpressionGroup orIsNull(PFun name) { + return or(isNull(name)); + } + public SqlExpressionGroup orNotIsNull(String name) { return or(isNull(name).not()); } + public SqlExpressionGroup orNotIsNull(PFun name) { + return or(isNull(name).not()); + } + public SqlExpressionGroup orGT(String name, long val) { return or(gt(name, val)); } + public SqlExpressionGroup orGT(PFun name, long val) { + return or(gt(name, val)); + } + public SqlExpressionGroup orGTE(String name, long val) { return or(gte(name, val)); } + public SqlExpressionGroup orGTE(PFun name, long val) { + return or(gte(name, val)); + } + public SqlExpressionGroup orLT(String name, long val) { return or(lt(name, val)); } + public SqlExpressionGroup orLT(PFun name, long val) { + return or(lt(name, val)); + } + public SqlExpressionGroup orLTE(String name, long val) { return or(lte(name, val)); } + public SqlExpressionGroup orLTE(PFun name, long val) { + return or(lte(name, val)); + } + public SqlExpressionGroup orIn(String name, long... ids) { return or(inLong(name, ids)); } + public SqlExpressionGroup orIn(PFun name, long... ids) { + return or(inLong(name, ids)); + } + public SqlExpressionGroup orIn(String name, int... ids) { return or(inInt(name, ids)); } + public SqlExpressionGroup orIn(PFun name, int... ids) { + return or(inInt(name, ids)); + } + public SqlExpressionGroup orIn(String name, String... names) { return or(inStr(name, names)); } + public SqlExpressionGroup orIn(PFun name, String... names) { + return or(inStr(name, names)); + } + public SqlExpressionGroup orInBySql(String name, String subSql, Object... args) { return or(inSql(name, subSql, args)); } + public SqlExpressionGroup orInBySql(PFun name, String subSql, Object... args) { + return or(inSql(name, subSql, args)); + } + public SqlExpressionGroup orNotInBySql(String name, String subSql, Object... args) { return or(inSql(name, subSql, args).not()); } + public SqlExpressionGroup orNotInBySql(PFun name, String subSql, Object... args) { + return or(inSql(name, subSql, args).not()); + } + public SqlExpressionGroup orInBySql2(String name, String subSql, Object... values) { return or(inSql2(name, subSql, values)); } + public SqlExpressionGroup orInBySql2(PFun name, String subSql, Object... values) { + return or(inSql2(name, subSql, values)); + } + public SqlExpressionGroup orNotInBySql2(String name, String subSql, Object... values) { return or(inSql2(name, subSql, values).not()); } + public SqlExpressionGroup orNotInBySql2(PFun name, String subSql, Object... values) { + return or(inSql2(name, subSql, values).not()); + } + public SqlExpressionGroup orNotIn(String name, long... ids) { return or(inLong(name, ids).not()); } + public SqlExpressionGroup orNotIn(PFun name, long... ids) { + return or(inLong(name, ids).not()); + } + public SqlExpressionGroup orNotIn(String name, int... ids) { return or(inInt(name, ids).not()); } + public SqlExpressionGroup orNotIn(PFun name, int... ids) { + return or(inInt(name, ids).not()); + } + public SqlExpressionGroup orNotIn(String name, String... names) { return or(inStr(name, names).not()); } + public SqlExpressionGroup orNotIn(PFun name, String... names) { + return or(inStr(name, names).not()); + } + public SqlExpressionGroup orLike(String name, String value) { return or(like(name, value)); } - + + public SqlExpressionGroup orLikeL(PFun name, String value) { + return or(like(name, value).left(null)); + } + public SqlExpressionGroup orLikeL(String name, String value) { return or(like(name, value).left(null)); } - + public SqlExpressionGroup orLikeR(String name, String value) { return or(like(name, value).right(null)); } + public SqlExpressionGroup orLikeR(PFun name, String value) { + return or(like(name, value).right(null)); + } + public SqlExpressionGroup orNotLike(String name, String value) { return or(like(name, value).not()); } + public SqlExpressionGroup orNotLike(PFun name, String value) { + return or(like(name, value).not()); + } + public SqlExpressionGroup orNotLikeL(String name, String value) { return or(like(name, value).left(null).not()); } + public SqlExpressionGroup orNotLikeL(PFun name, String value) { + return or(like(name, value).left(null).not()); + } + public SqlExpressionGroup orNotLikeR(String name, String value) { return or(like(name, value).right(null).not()); } + public SqlExpressionGroup orNotLikeR(PFun name, String value) { + return or(like(name, value).right(null).not()); + } + public SqlExpressionGroup orLike(String name, String value, boolean ignoreCase) { return or(like(name, value, ignoreCase)); } + public SqlExpressionGroup orLike(PFun name, String value, boolean ignoreCase) { + return or(like(name, value, ignoreCase)); + } + public SqlExpressionGroup orNotLike(String name, String value, boolean ignoreCase) { return or(like(name, value, ignoreCase).not()); } - + + public SqlExpressionGroup orNotLike(PFun name, String value, boolean ignoreCase) { + return or(like(name, value, ignoreCase).not()); + } + public SqlExpressionGroup orLike(String name, String value, String left, String right, boolean ignoreCase) { return or(like(name, value, ignoreCase).left(left).right(right)); } - + + public SqlExpressionGroup orLike(PFun name, String value, String left, String right, boolean ignoreCase) { + return or(like(name, value, ignoreCase).left(left).right(right)); + } + public SqlExpressionGroup orNotLike(String name, String value, String left, String right, boolean ignoreCase) { return or(like(name, value, ignoreCase).left(left).right(right).not()); } - + + public SqlExpressionGroup orNotLike(PFun name, String value, String left, String right, boolean ignoreCase) { + return or(like(name, value, ignoreCase).left(left).right(right).not()); + } + //------------------ between public SqlExpressionGroup andBetween(String name, Object min, Object max) { return and(new BetweenExpression(name, min, max)); } - + + public SqlExpressionGroup andBetween(PFun name, Object min, Object max) { + return and(new BetweenExpression(name, min, max)); + } + public SqlExpressionGroup orBetween(String name, Object min, Object max) { return or(new BetweenExpression(name, min, max)); } + public SqlExpressionGroup orBetween(PFun name, Object min, Object max) { + return or(new BetweenExpression(name, min, max)); + } + @Override public void setPojo(Pojo pojo) { super.setPojo(pojo); @@ -368,11 +710,11 @@ public boolean isEmpty() { public List cloneExps() { return new ArrayList(exps); } - + public List getExps() { return exps; } - + public SqlExpressionGroup clone(){ SqlExpressionGroup seg = new SqlExpressionGroup(); seg.exps = cloneExps(); @@ -380,7 +722,7 @@ public SqlExpressionGroup clone(){ seg.top = this.top; return seg; } - + /** * 若value为null/空白字符串/空集合/空数组,则本条件不添加. * @see Cnd#and(String, String, Object) @@ -388,7 +730,15 @@ public SqlExpressionGroup clone(){ public SqlExpressionGroup andEX(String name, String op, Object value) { return and(Cnd.expEX(name, op, value)); } - + + /** + * 若value为null/空白字符串/空集合/空数组,则本条件不添加. + * @see Cnd#and(String, String, Object) + */ + public SqlExpressionGroup andEX(PFun name, String op, Object value) { + return and(Cnd.expEX(name, op, value)); + } + /** * 若value为null/空白字符串/空集合/空数组,则本条件不添加. * @see Cnd#or(String, String, Object) @@ -396,4 +746,12 @@ public SqlExpressionGroup andEX(String name, String op, Object value) { public SqlExpressionGroup orEX(String name, String op, Object value) { return or(Cnd.expEX(name, op, value)); } + + /** + * 若value为null/空白字符串/空集合/空数组,则本条件不添加. + * @see Cnd#or(String, String, Object) + */ + public SqlExpressionGroup orEX(PFun name, String op, Object value) { + return or(Cnd.expEX(name, op, value)); + } } diff --git a/src/org/nutz/dao/util/cri/SqlRange.java b/src/org/nutz/dao/util/cri/SqlRange.java index 0900c7339c..04517f760c 100644 --- a/src/org/nutz/dao/util/cri/SqlRange.java +++ b/src/org/nutz/dao/util/cri/SqlRange.java @@ -1,17 +1,34 @@ package org.nutz.dao.util.cri; import org.nutz.dao.entity.Entity; +import org.nutz.dao.util.lambda.PFun; public class SqlRange extends NoParamsSqlExpression implements SqlExpression { private static final long serialVersionUID = 1L; - private String sql; + protected String sql; + + protected SqlRange(String name) { + super(name); + } + + protected SqlRange(PFun name) { + super(name); + } public SqlRange(String name, String fmt, Object... args) { super(name); this.not = false; - this.sql = String.format(fmt, args); + if (fmt != null) + this.sql = String.format(fmt, args); + } + + public SqlRange(PFun name, String fmt, Object... args) { + super(name); + this.not = false; + if (fmt != null) + this.sql = String.format(fmt, args); } public void joinSql(Entity en, StringBuilder sb) { diff --git a/src/org/nutz/dao/util/cri/SqlValueRange.java b/src/org/nutz/dao/util/cri/SqlValueRange.java index cf1d0bcf2b..49271feabf 100644 --- a/src/org/nutz/dao/util/cri/SqlValueRange.java +++ b/src/org/nutz/dao/util/cri/SqlValueRange.java @@ -4,16 +4,17 @@ import org.nutz.dao.entity.MappingField; import org.nutz.dao.jdbc.Jdbcs; import org.nutz.dao.jdbc.ValueAdaptor; +import org.nutz.dao.util.lambda.PFun; import org.nutz.lang.Strings; public class SqlValueRange extends AbstractSqlExpression { private static final long serialVersionUID = 6899168558668898529L; - + protected String sql; protected Object[] values; protected int size; - + protected SqlValueRange(String name, String sql, Object... values) { super(name); this.sql = sql; @@ -21,6 +22,13 @@ protected SqlValueRange(String name, String sql, Object... values) { this.size = values.length; } + protected SqlValueRange(PFun name, String sql, Object... values) { + super(name); + this.sql = sql; + this.values = values; + this.size = values.length; + } + public void joinSql(Entity en, StringBuilder sb) { if (size == 0) return; diff --git a/src/org/nutz/dao/util/lambda/LambdaQuery.java b/src/org/nutz/dao/util/lambda/LambdaQuery.java new file mode 100644 index 0000000000..a54515dce2 --- /dev/null +++ b/src/org/nutz/dao/util/lambda/LambdaQuery.java @@ -0,0 +1,86 @@ +package org.nutz.dao.util.lambda; + +import java.lang.invoke.SerializedLambda; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +/** + * @author 黄川 huchuc@vip.qq.com + * @date: 2021/4/21 + */ +public class LambdaQuery { + + /** + * 字段映射 + */ + private static final Map COLUMN_CACHE_MAP = new ConcurrentHashMap<>(); + + + /** + * @param 类型,被调用的 Function 对象的目标类型 + * @param lambda 需要解析的 lambda 对象 + * @return 返回解析后的字段名称 + */ + public static String resolve(PFun lambda) { + Class clazz = lambda.getClass(); + String className = clazz.getName(); + return Optional.ofNullable(COLUMN_CACHE_MAP.get(className)).orElseGet(() -> getPropertyName(className, lambda)); + } + + /** + * @param 类型,被调用的 Function 对象的目标类型 + * @param lambda 需要解析的 lambda 对象 + * @return 返回解析后的字段名称 + */ + public static String[] resolves(PFun... lambda) { + return Arrays.stream(lambda).map(LambdaQuery::resolve).collect(Collectors.toList()).toArray(new String[0]); + } + + /** + * 获取字段名称 + * + * @param className + * @param lambda + * @param + * @return + */ + private static String getPropertyName(String className, PFun lambda) { + if (!lambda.getClass().isSynthetic()) { + throw new RuntimeException("该方法仅能传入 lambda 表达式产生的合成类"); + } + try { + Method writeReplace = lambda.getClass().getDeclaredMethod("writeReplace"); + writeReplace.setAccessible(true); + SerializedLambda serializedLambda = (SerializedLambda) writeReplace.invoke(lambda); + return COLUMN_CACHE_MAP.computeIfAbsent(className, s -> methodNameToPropertyName(serializedLambda.getImplMethodName())); + } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { + throw new RuntimeException("不可能发生的异常!!!"); + } + } + + /** + * 方法名转属性名 + * + * @return + */ + @SuppressWarnings("all") + public static String methodNameToPropertyName(String methodName) { + if (methodName.startsWith("is")) { + methodName = methodName.substring(2); + } else if (methodName.startsWith("get") || methodName.startsWith("set")) { + methodName = methodName.substring(3); + } else { + throw new RuntimeException("方法名'" + methodName + "'不是以 'is','get','set' 开始的!"); + } + if (methodName.length() == 1 || (methodName.length() > 1 && !Character.isUpperCase(methodName.charAt(1)))) { + methodName = methodName.substring(0, 1).toLowerCase(Locale.ENGLISH) + methodName.substring(1); + } + return methodName; + } +} diff --git a/src/org/nutz/dao/util/lambda/PFun.java b/src/org/nutz/dao/util/lambda/PFun.java new file mode 100644 index 0000000000..0dc51f6466 --- /dev/null +++ b/src/org/nutz/dao/util/lambda/PFun.java @@ -0,0 +1,25 @@ +package org.nutz.dao.util.lambda; + +import java.io.Serializable; +import java.util.function.Function; + +/** + * @author 黄川 huchuc@vip.qq.com + * @date: 2021/4/21 + *

+ * serializedLambda:{ + * "capturingClass": "org/nutz/spring/boot/cnd/ConditionCall", + * "functionalInterfaceClass": "org/nutz/spring/boot/cnd/ParamsFunction", + * "functionalInterfaceMethodName": "apply", + * "functionalInterfaceMethodSignature": "(Ljava/lang/Object;)Ljava/lang/Object;", + * "implClass": "org/nutz/spring/boot/dao/test/entity/TestDO", + * "implMethodName": "getId", + * "implMethodSignature": "()Ljava/lang/Integer;", + * "implMethodKind": 5, + * "instantiatedMethodType": "(Lorg/nutz/spring/boot/dao/test/entity/TestDO;)Ljava/lang/Integer;", + * "capturedArgs": [] + * } + */ +@FunctionalInterface +public interface PFun extends Function, Serializable { +} diff --git a/src/org/nutz/dao/util/tables/TablesFilter.java b/src/org/nutz/dao/util/tables/TablesFilter.java new file mode 100644 index 0000000000..f694fe5e22 --- /dev/null +++ b/src/org/nutz/dao/util/tables/TablesFilter.java @@ -0,0 +1,21 @@ +package org.nutz.dao.util.tables; + +import org.nutz.dao.entity.annotation.Table; + +/** + * 通过Daos辅助函数自动创建表时,对不需要自动创建得表进行过滤 + * + * @author 黄川(huchuc@vip.qq.com) + */ +public interface TablesFilter { + + /** + * 效验表信息判断是否执行过滤 + * @param klass + * 实体类 + * @param table + * 实体类的@Table信息 + * @return + */ + boolean match(Class klass,Table table); +} diff --git a/src/org/nutz/el/El.java b/src/org/nutz/el/El.java index 8cb3b882e6..1ef311564b 100644 --- a/src/org/nutz/el/El.java +++ b/src/org/nutz/el/El.java @@ -1,10 +1,13 @@ package org.nutz.el; +import java.lang.reflect.Method; import java.util.Map; import java.util.Queue; -import org.nutz.el.arithmetic.ShuntingYard; import org.nutz.el.arithmetic.RPN; +import org.nutz.el.arithmetic.ShuntingYard; +import org.nutz.el.opt.RunMethod; +import org.nutz.el.opt.custom.CustomMake; import org.nutz.lang.Lang; import org.nutz.lang.segment.CharSegment; import org.nutz.lang.util.Context; @@ -70,9 +73,9 @@ public static String render(String seg, Context ctx) { public static String render(CharSegment seg, Context ctx) { Context main = Lang.context(); for (String key : seg.keys()) { - main.putAll(key, new El(key).eval(ctx)); + main.set(key, new El(key).eval(ctx)); } - return seg.render(main).toString(); + return String.valueOf(seg.render(main)); } public static String render(CharSegment seg, Map els, Context ctx) { @@ -81,8 +84,16 @@ public static String render(CharSegment seg, Map els, Context ctx) { El el = els.get(key); if (el == null) el = new El(key); - main.putAll(key, el.eval(ctx)); + main.set(key, el.eval(ctx)); } - return seg.render(main).toString(); + return String.valueOf(seg.render(main)); + } + + public static void register(String name, RunMethod run) { + CustomMake.me().register(name, run); + } + + public static void register(String name, Method method) { + CustomMake.me().register(name, method); } } diff --git a/src/org/nutz/el/opt/arithmetic/NegativeOpt.java b/src/org/nutz/el/opt/arithmetic/NegativeOpt.java index d1aa7ad0ed..f83fd040df 100644 --- a/src/org/nutz/el/opt/arithmetic/NegativeOpt.java +++ b/src/org/nutz/el/opt/arithmetic/NegativeOpt.java @@ -3,6 +3,7 @@ import java.util.Queue; import org.nutz.el.opt.AbstractOpt; +import org.nutz.el.opt.object.CommaOpt; /** * 负号:'-' @@ -39,6 +40,12 @@ public static boolean isNegetive(Object prev){ if(prev == null){ return true; } + if (prev instanceof Object[]) { + Object[] tmp = (Object[])prev; + if (tmp.length == 0) + return true; + prev = tmp[tmp.length - 1];// 最后一个 + } if(prev instanceof LBracketOpt){ return true; } @@ -57,6 +64,8 @@ public static boolean isNegetive(Object prev){ if(prev instanceof SubOpt){ return true; } + if (prev instanceof CommaOpt) + return true; return false; } diff --git a/src/org/nutz/el/opt/custom/DoBase64.java b/src/org/nutz/el/opt/custom/DoBase64.java index ac6d985316..1c4116d515 100644 --- a/src/org/nutz/el/opt/custom/DoBase64.java +++ b/src/org/nutz/el/opt/custom/DoBase64.java @@ -12,6 +12,7 @@ * @author wendal(wendal1985@gmail.com) * */ + public class DoBase64 implements RunMethod, Plugin { public boolean canWork() { diff --git a/src/org/nutz/el/opt/logic/AndOpt.java b/src/org/nutz/el/opt/logic/AndOpt.java index e1b41f3474..c0a5482a3c 100644 --- a/src/org/nutz/el/opt/logic/AndOpt.java +++ b/src/org/nutz/el/opt/logic/AndOpt.java @@ -21,9 +21,10 @@ public Object calculate() { if (!(lval instanceof Boolean)) { // throw new ElException("操作数类型错误!"); - return Castors.me().castTo(lval, Boolean.class); - } - if (!(Boolean) lval) { + if (!Castors.me().castTo(lval, Boolean.class)) { + return false; + } + } else if (!(Boolean) lval) { return false; } @@ -34,10 +35,7 @@ public Object calculate() { // throw new ElException("操作数类型错误!"); return Castors.me().castTo(rval, Boolean.class); } - if (!(Boolean) rval) { - return false; - } - return true; + return (Boolean) rval; } public String fetchSelf() { diff --git a/src/org/nutz/el/opt/logic/OrOpt.java b/src/org/nutz/el/opt/logic/OrOpt.java index b81dbfac1a..043b23a1c4 100644 --- a/src/org/nutz/el/opt/logic/OrOpt.java +++ b/src/org/nutz/el/opt/logic/OrOpt.java @@ -20,9 +20,10 @@ public Object calculate() { if (null != lval) { if (!(lval instanceof Boolean)) { // throw new ElException("操作数类型错误!"); - return Castors.me().castTo(lval, Boolean.class); - } - if ((Boolean) lval) { + if (Castors.me().castTo(lval, Boolean.class)) { + return true; + } + } else if ((Boolean) lval) { return true; } } diff --git a/src/org/nutz/el/parse/CharQueue.java b/src/org/nutz/el/parse/CharQueue.java index 370891f0c8..6d972c203c 100644 --- a/src/org/nutz/el/parse/CharQueue.java +++ b/src/org/nutz/el/parse/CharQueue.java @@ -11,10 +11,10 @@ public interface CharQueue { */ char peek(); /** - * 不删除字符的情况下读取第ofset个字符, - * @param ofset 偏移量 + * 不删除字符的情况下读取第offset个字符, + * @param offset 偏移量 */ - char peek(int ofset); + char peek(int offset); /** * 读取字符,并删除字符 */ diff --git a/src/org/nutz/el/parse/IdentifierParse.java b/src/org/nutz/el/parse/IdentifierParse.java index 3a1779fe4a..76cd67ce76 100644 --- a/src/org/nutz/el/parse/IdentifierParse.java +++ b/src/org/nutz/el/parse/IdentifierParse.java @@ -11,6 +11,7 @@ */ public class IdentifierParse implements Parse{ + @Override public Object fetchItem(CharQueue exp) { StringBuilder sb = new StringBuilder(); if(Character.isJavaIdentifierStart(exp.peek())){ @@ -18,13 +19,13 @@ public Object fetchItem(CharQueue exp) { while(!exp.isEmpty() && Character.isJavaIdentifierPart(exp.peek())){ sb.append(exp.poll()); } - if(sb.toString().equals("null")){ + if("null".equals(sb.toString())){ return new IdentifierObj(null); } - if(sb.toString().equals("true")){ + if("true".equals(sb.toString())){ return Boolean.TRUE; } - if(sb.toString().equals("false")){ + if("false".equals(sb.toString())){ return Boolean.FALSE; } return new AbstractObj(sb.toString()); diff --git a/src/org/nutz/el/parse/OptParse.java b/src/org/nutz/el/parse/OptParse.java index a7af7723a4..722c6d4cf4 100644 --- a/src/org/nutz/el/parse/OptParse.java +++ b/src/org/nutz/el/parse/OptParse.java @@ -41,123 +41,131 @@ */ public class OptParse implements Parse { + @Override public Object fetchItem(CharQueue exp){ - switch(exp.peek()){ - case '+': - exp.poll(); - return new PlusOpt(); - case '-': - exp.poll(); - return new SubOpt(); - case '*': - exp.poll(); - return new MulOpt(); - case '/': - exp.poll(); - return new DivOpt(); - case '%': - exp.poll(); - return new ModOpt(); - case '(': - exp.poll(); - return new LBracketOpt(); - case ')': - exp.poll(); - return new RBracketOpt(); - case '>': - exp.poll(); - switch(exp.peek()){ - case '=': + switch (exp.peek()) { + case '+': + exp.poll(); + return new PlusOpt(); + case '-': + exp.poll(); + return new SubOpt(); + case '*': + exp.poll(); + return new MulOpt(); + case '/': + exp.poll(); + return new DivOpt(); + case '%': + exp.poll(); + return new ModOpt(); + case '(': + exp.poll(); + return new LBracketOpt(); + case ')': exp.poll(); - return new GTEOpt(); + return new RBracketOpt(); case '>': exp.poll(); - if(exp.peek() == '>'){ - exp.poll(); - return new UnsignedLeftShift(); + switch (exp.peek()) { + case '=': + exp.poll(); + return new GTEOpt(); + case '>': + exp.poll(); + if (exp.peek() == '>') { + exp.poll(); + return new UnsignedLeftShift(); + } + return new RightShift(); + default: } - return new RightShift(); - } - return new GTOpt(); - case '<': - exp.poll(); - switch(exp.peek()){ - case '=': - exp.poll(); - return new LTEOpt(); + return new GTOpt(); case '<': exp.poll(); - return new LeftShift(); - } - return new LTOpt(); - case '=': - exp.poll(); - switch(exp.peek()){ - case '=': - exp.poll(); - return new EQOpt(); - } - throw new ElException("表达式错误,请检查'='后是否有非法字符!"); - case '!': - exp.poll(); - switch(exp.peek()){ + switch (exp.peek()) { + case '=': + exp.poll(); + return new LTEOpt(); + case '<': + exp.poll(); + return new LeftShift(); + default: + } + return new LTOpt(); case '=': exp.poll(); - return new NEQOpt(); + switch (exp.peek()) { + case '=': + exp.poll(); + return new EQOpt(); + default: + } + throw new ElException("表达式错误,请检查'='后是否有非法字符!"); case '!': exp.poll(); - return new NullableOpt(); - } - return new NotOpt(); - case '|': - exp.poll(); - switch(exp.peek()){ + switch (exp.peek()) { + case '=': + exp.poll(); + return new NEQOpt(); + case '!': + exp.poll(); + return new NullableOpt(); + default: + } + return new NotOpt(); case '|': exp.poll(); - if (exp.peek() == '|') { - exp.poll(); - return new OrOpt2(); + switch (exp.peek()) { + case '|': + exp.poll(); + if (exp.peek() == '|') { + exp.poll(); + return new OrOpt2(); + } + return new OrOpt(); + default: } - return new OrOpt(); - } - return new BitOr(); - case '&': - exp.poll(); - switch(exp.peek()){ + return new BitOr(); case '&': exp.poll(); - return new AndOpt(); - } - return new BitAnd(); - case '~': - exp.poll(); - return new BitNot(); - case '^': - exp.poll(); - return new BitXro(); - case '?': - exp.poll(); - return new QuestionOpt(); - case ':': - exp.poll(); - return new QuestionSelectOpt(); - - case '.': - char p = exp.peek(1); - if (p != '\'' && p != '"' && !Character.isJavaIdentifierStart(p)){ - return nullobj; - } - exp.poll(); - return new AccessOpt(); - case ',': - exp.poll(); - return new CommaOpt(); - case '[': - exp.poll(); - return new Object[]{new ArrayOpt(),new LBracketOpt()}; - case ']': - exp.poll(); - return new Object[]{new RBracketOpt(), new FetchArrayOpt()}; + switch (exp.peek()) { + case '&': + exp.poll(); + return new AndOpt(); + default: + } + return new BitAnd(); + case '~': + exp.poll(); + return new BitNot(); + case '^': + exp.poll(); + return new BitXro(); + case '?': + exp.poll(); + return new QuestionOpt(); + case ':': + exp.poll(); + return new QuestionSelectOpt(); + + case '.': + char p = exp.peek(1); + if (p != '\'' && p != '"' && !Character.isJavaIdentifierStart(p)) { + return nullobj; + } + exp.poll(); + return new AccessOpt(); + case ',': + exp.poll(); + return new CommaOpt(); + case '[': + exp.poll(); + return new Object[]{new ArrayOpt(), new LBracketOpt()}; + case ']': + exp.poll(); + return new Object[]{new RBracketOpt(), new FetchArrayOpt()}; + default: } return nullobj; } diff --git a/src/org/nutz/el/parse/StringParse.java b/src/org/nutz/el/parse/StringParse.java index 250daa27b2..5f356b9ac3 100644 --- a/src/org/nutz/el/parse/StringParse.java +++ b/src/org/nutz/el/parse/StringParse.java @@ -9,23 +9,25 @@ * */ public class StringParse implements Parse { + @Override public Object fetchItem(CharQueue exp) { //@ JKTODO 添加转意字符 - switch(exp.peek()){ - case '\'': - case '"': - StringBuilder sb = new StringBuilder(); - char end = exp.poll(); + switch (exp.peek()) { + case '\'': + case '"': + StringBuilder sb = new StringBuilder(); + char end = exp.poll(); // while(!exp.isEmpty() && !exp.peek().equals(end)){ - while(!exp.isEmpty() && exp.peek() != end){ - if(exp.peek() == '\\') {//转义字符? - parseSp(exp, sb); - }else{ - sb.append(exp.poll()); + while (!exp.isEmpty() && exp.peek() != end) { + if (exp.peek() == '\\') {//转义字符? + parseSp(exp, sb); + } else { + sb.append(exp.poll()); + } } - } - exp.poll(); - return sb.toString(); + exp.poll(); + return sb.toString(); + default: } return nullobj; } @@ -52,8 +54,9 @@ private void parseSp(CharQueue exp, StringBuilder sb){ break; case 'u': char[] hex = new char[4]; - for (int i = 0; i < 4; i++) + for (int i = 0; i < 4; i++) { hex[i] = exp.poll(); + } sb.append((char)Integer.valueOf(new String(hex), 16).intValue()); break; case 'b': //这个支持一下又何妨? diff --git a/src/org/nutz/el/parse/ValParse.java b/src/org/nutz/el/parse/ValParse.java index aefe760581..ec01f30990 100644 --- a/src/org/nutz/el/parse/ValParse.java +++ b/src/org/nutz/el/parse/ValParse.java @@ -10,66 +10,68 @@ */ public class ValParse implements Parse { + @Override public Object fetchItem(CharQueue exp){ StringBuilder sb = new StringBuilder(); - switch(exp.peek()){ - case '.': - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - boolean hasPoint = exp.peek() == '.'; - sb.append(exp.poll()); - while(!exp.isEmpty()){ - switch(exp.peek()){ - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - sb.append(exp.poll()); - break; - case '.': - if(hasPoint){ - throw new ElException("表达式错误,请查看是否有多个'.'!"); + switch (exp.peek()) { + case '.': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + boolean hasPoint = exp.peek() == '.'; + sb.append(exp.poll()); + while (!exp.isEmpty()) { + switch (exp.peek()) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + sb.append(exp.poll()); + break; + case '.': + if (hasPoint) { + throw new ElException("表达式错误,请查看是否有多个'.'!"); + } + hasPoint = true; + sb.append(exp.poll()); + break; + case 'l': + case 'L': + sb.append(exp.poll()); + return Long.parseLong(sb.toString()); + case 'f': + case 'F': + sb.append(exp.poll()); + return Float.parseFloat(sb.toString()); + case 'd': + case 'D': + sb.append(exp.poll()); + return Double.parseDouble(sb.toString()); + default: + if (hasPoint) { + return Double.parseDouble(sb.toString()); + } + return Integer.parseInt(sb.toString()); } - hasPoint = true; - sb.append(exp.poll()); - break; - case 'l': - case 'L': - sb.append(exp.poll()); - return Long.parseLong(sb.toString()); - case 'f': - case 'F': - sb.append(exp.poll()); - return Float.parseFloat(sb.toString()); - case 'd': - case 'D': - sb.append(exp.poll()); + } + if (hasPoint) { return Double.parseDouble(sb.toString()); - default: - if(hasPoint){ - return Double.parseDouble(sb.toString()); - } - return Integer.parseInt(sb.toString()); } - } - if(hasPoint){ - return Double.parseDouble(sb.toString()); - } - return Integer.parseInt(sb.toString()); + return Integer.parseInt(sb.toString()); + default: } return nullobj; } diff --git a/src/org/nutz/filepool/NutFilePool.java b/src/org/nutz/filepool/NutFilePool.java index def27bb608..7b41ab17cb 100644 --- a/src/org/nutz/filepool/NutFilePool.java +++ b/src/org/nutz/filepool/NutFilePool.java @@ -1,14 +1,17 @@ package org.nutz.filepool; import java.io.File; -import java.io.FilenameFilter; import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.nutz.lang.Files; import org.nutz.lang.Lang; import org.nutz.lang.util.Disks; +import org.nutz.lang.util.Regex; import org.nutz.log.Log; import org.nutz.log.Logs; @@ -39,30 +42,9 @@ public NutFilePool(String homePath, long size) { log.debugf("file-pool.home: '%s'", home.getAbsolutePath()); } - File last = home; - String[] subs = null; - while (last.isDirectory()) { - subs = last.list(new FilenameFilter() { - public boolean accept(File dir, String name) { - return name.matches("^([\\d|A-F]{2})([.][a-zA-Z]{1,})?$"); - } - }); - if (null != subs && subs.length > 0) { - String lastName = "00"; - for (String sub : subs) { - if (sub.compareTo(lastName) > 0) { - lastName = sub; - } - } - last = new File(last.getAbsolutePath() + "/" + lastName); - if (last.isFile()) { - cursor = Pools.getFileId(home, last); - break; - } - } else { - break; - } - } + cursor = foundMax(home, home, 0); + if (cursor < 0) + cursor = 0; if (log.isInfoEnabled()) log.infof("file-pool.cursor: %s", cursor); @@ -79,11 +61,9 @@ public void clear() { } public File createFile(String suffix) { - if (size > 0 && cursor >= size) + if (size > 0 && cursor >= size-1) cursor = -1; long id = ++cursor; - if (size > 0 && id >= size) - Lang.makeThrow("Id (%d) is out of range (%d)", id, size); File re = Pools.getFileById(home, id, suffix); if (!re.exists()) try { @@ -200,4 +180,35 @@ public static FilePool getOrCreatePool(String path, long limit) { public static void clearPools() { pools.clear(); } + + protected static long foundMax(File home, File current, int level) { + // 最后一层了 + if (level == 8) { + if (current.isDirectory()) + return -1; + //System.out.println("found File!! "+current); + return Pools.getFileId(home, current); + } + if (!current.isDirectory()) + return -1; + int next_level = level+1; + List names = new ArrayList(); + + for (File f : current.listFiles()) { + if (Regex.match("^([\\d|A-F]{2})([.][a-zA-Z]{1,})?$", f.getName())) { + names.add(f.getName()); + } + } + Collections.sort(names); + Collections.reverse(names); + for (String name : names) { + File next = new File(current, name); + //System.out.println(next + ", level=" + next_level); + long max = foundMax(home, next, next_level); + if (max > -1) { + return max; + } + } + return -1; + } } diff --git a/src/org/nutz/http/Cookie.java b/src/org/nutz/http/Cookie.java index 5dbc9bf856..152845fc73 100644 --- a/src/org/nutz/http/Cookie.java +++ b/src/org/nutz/http/Cookie.java @@ -5,8 +5,10 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import org.nutz.json.Json; +import org.nutz.lang.Lang; import org.nutz.lang.Strings; import org.nutz.lang.meta.Pair; import org.nutz.log.Log; @@ -16,12 +18,17 @@ public class Cookie implements HttpReqRespInterceptor { private static final Log log = Logs.get(); + private static Set keyWords = Lang.set("expires", "domain", "path", "secure", "httponly", "samesite"); + protected Map map; protected boolean debug; + + protected boolean ignoreNull; public Cookie() { map = new HashMap(); + ignoreNull = true; // 和之前版本行为保持一致 } public Cookie(String s) { @@ -44,30 +51,36 @@ public Cookie set(String name, String value) { } public void parse(String str) { - if (debug) + if (debug) { log.debug("parse " + str); + } String[] ss = Strings.splitIgnoreBlank(str, ";"); for (String s : ss) { Pair p = Pair.create(Strings.trim(s)); - if (p.getValueString() == null) + if (p.getValueString() == null && ignoreNull) { continue; - if ("Path".equals(p.getName()) || "Expires".equals(p.getName())) + } + if (keyWords.contains(p.getName().toLowerCase())) continue; - if ("Max-Age".equals(p.getName())) { + if ("Max-Age".equalsIgnoreCase(p.getName())) { long age = Long.parseLong(p.getValue()); - if (age == 0) + if (age == 0) { return; + } } - String val = p.getValueString(); - if (debug) + String val = Strings.sNull(p.getValueString()); + if (debug) { log.debugf("add cookie [%s=%s]", p.getName(), val); + } map.put(p.getName(), val); } } - + + @Override public String toString() { - if (map.isEmpty()) + if (map.isEmpty()) { return ""; + } StringBuilder sb = new StringBuilder(); for (Entry en : map.entrySet()) { sb.append(en.getKey()).append('=').append(en.getValue()).append("; "); @@ -76,28 +89,35 @@ public String toString() { return sb.toString(); } + @Override public void beforeConnect(Request request) { } + @Override public void afterConnect(Request request, HttpURLConnection conn) { - if (this.map.isEmpty()) + if (this.map.isEmpty()) { return; + } String c = toString(); - if (debug) + if (debug) { log.debugf("add Cookie for req [%s]", c); - if (!Strings.isBlank(c)) + } + if (!Strings.isBlank(c)) { conn.addRequestProperty("Cookie", c); + } } + @Override public void afterResponse(Request request, HttpURLConnection conn, Response response) { Map> props = conn.getHeaderFields(); for (Entry> en : props.entrySet()) { - if (en.getKey() == null || !en.getKey().equalsIgnoreCase("Set-Cookie")) { + if (en.getKey() == null || !"Set-Cookie".equalsIgnoreCase(en.getKey())) { continue; } for (String e : en.getValue()) { - if (debug) + if (debug) { log.debugf("found Set-Cookie [%s]", e); + } this.parse(e); } break; @@ -115,4 +135,8 @@ public int size() { public void setDebug(boolean debug) { this.debug = debug; } + + public void setIgnoreNull(boolean ignoreNull) { + this.ignoreNull = ignoreNull; + } } diff --git a/src/org/nutz/http/Header.java b/src/org/nutz/http/Header.java index 4872fbf9c1..6aa0c2966e 100644 --- a/src/org/nutz/http/Header.java +++ b/src/org/nutz/http/Header.java @@ -1,29 +1,52 @@ package org.nutz.http; import java.nio.charset.Charset; +import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.nutz.json.Json; import org.nutz.json.JsonFormat; +import org.nutz.lang.util.NutMap; +@SuppressWarnings("unchecked") public class Header { + protected NutMap items; + protected Header() { - items = new HashMap(); + items = new NutMap(); } - private Map items; - public Collection keys() { return items.keySet(); } + public NutMap getMap() { + NutMap map = new NutMap(); + for (String key : this.keys()) { + String val = this.get(key); + map.put(key, val); + } + return map; + } + + @SuppressWarnings("rawtypes") public String get(String key) { - return items.get(key); + Object value = items.get(key); + if (value == null) + return null; + if (value instanceof List) { + if (((List) value).isEmpty()) + return null; + return (String) ((List) value).get(0); + } + return (String) value; } public Header set(String key, String value) { @@ -43,12 +66,22 @@ public Header clear() { } public Set> getAll() { - return items.entrySet(); + Map tmp = new HashMap(); + for (String key : items.keySet()) { + String value = get(key); + if (value != null) + tmp.put(key, value); + } + return tmp.entrySet(); } public Header addAll(Map map) { - if (null != map) - items.putAll(map); + if (null != map) { + for (Map.Entry en : map.entrySet()) { + if (en.getValue() != null) // 如果值不是String,就立马报错咯 + this.items.put(en.getKey(), en.getValue()); + } + } return this; } @@ -61,7 +94,12 @@ public static Header create(Map properties) { return new Header().addAll(properties); } - @SuppressWarnings("unchecked") + public static Header create(NutMap reHeader) { + Header header = new Header(); + header.items.putAll(reHeader); + return header; + } + public static Header create(String properties) { return create((Map) Json.fromJson(properties)); } @@ -85,26 +123,43 @@ public int getInt(String key, int defaultValue) { return defaultValue; return Integer.parseInt(value); } - + public Header asJsonContentType() { return this.asJsonContentType(null); } - + public Header asFormContentType() { return this.asFormContentType(null); } - + public Header asJsonContentType(String enc) { if (enc == null) enc = Charset.defaultCharset().name(); - set("Content-Type", "application/json; charset="+enc.toUpperCase()); + set("Content-Type", "application/json; charset=" + enc.toUpperCase()); return this; } - + public Header asFormContentType(String enc) { if (enc == null) enc = Charset.defaultCharset().name(); - set("Content-Type", "application/x-www-form-urlencoded; charset="+enc.toUpperCase()); + set("Content-Type", "application/x-www-form-urlencoded; charset=" + enc.toUpperCase()); return this; } + + public void addv(String name, String value) { + if (value == null) { + items.remove(name); + } else { + items.addv(name, value); + } + } + + public List getValues(String name) { + Object value = items.get(name); + if (value == null) + return Collections.EMPTY_LIST; + if (value instanceof String) + return Arrays.asList((String) value); + return (List) value; + } } diff --git a/src/org/nutz/http/Http.java b/src/org/nutz/http/Http.java index 98824ad005..50b86277fd 100644 --- a/src/org/nutz/http/Http.java +++ b/src/org/nutz/http/Http.java @@ -12,7 +12,9 @@ import java.util.HashMap; import java.util.Map; +import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; @@ -224,7 +226,21 @@ public static Response post3(String url, Object body, Header header, int timeout } public static Response post3(String url, Object body, Header header, int timeout, int connTimeout) { - Request req = Request.create(url, METHOD.POST).setHeader(header); + return httpReq(url,METHOD.POST, body, header, timeout, Sender.Default_Conn_Timeout); + } + + /** + * 可定义请求方法 + * @param url 请求地址 + * @param method 请求方法 + * @param body 内容 + * @param header 请求头 + * @param timeout 超时时间 + * @param connTimeout + * @return + */ + public static Response httpReq(String url, Request.METHOD method, Object body, Header header, int timeout, int connTimeout) { + Request req = Request.create(url, method).setHeader(header); if (body != null) { if (body instanceof InputStream) { req.setInputStream((InputStream) body); @@ -234,7 +250,7 @@ public static Response post3(String url, Object body, Header header, int timeout req.setData(String.valueOf(body)); } } - return Sender.create(req).setTimeout(timeout).send(); + return Sender.create(req).setTimeout(timeout).setConnTimeout(connTimeout).send(); } public static Response upload(String url, @@ -375,6 +391,12 @@ public static void setProxySwitcher(ProxySwitcher proxySwitcher) { } protected static SSLSocketFactory sslSocketFactory; + protected static HostnameVerifier hostnameVerifier; + public static HostnameVerifier nopHostnameVerifier = new HostnameVerifier() { + public boolean verify(String hostname, SSLSession session) { + return true; + } + }; /** * 禁用JVM的https证书验证机制, 例如访问12306, 360 openapi之类的自签名证书 @@ -384,6 +406,7 @@ public static void setProxySwitcher(ProxySwitcher proxySwitcher) { public static boolean disableJvmHttpsCheck() { try { setSSLSocketFactory(nopSSLSocketFactory()); + hostnameVerifier = nopHostnameVerifier; } catch (Exception e) { return false; @@ -412,6 +435,10 @@ public static void setSSLSocketFactory(SSLSocketFactory sslSocketFactory) { Http.sslSocketFactory = sslSocketFactory; } + public static void setHostnameVerifier(HostnameVerifier hostnameVerifier) { + Http.hostnameVerifier = hostnameVerifier; + } + public static HashMap DEFAULT_HEADERS = new HashMap(); static { DEFAULT_HEADERS.put("User-Agent", "Nutz.Robot " + Nutz.version()); diff --git a/src/org/nutz/http/Request.java b/src/org/nutz/http/Request.java index a126c10ba2..e579cf3f95 100644 --- a/src/org/nutz/http/Request.java +++ b/src/org/nutz/http/Request.java @@ -15,11 +15,13 @@ import org.nutz.lang.ExitLoop; import org.nutz.lang.Lang; import org.nutz.lang.LoopException; +import org.nutz.lang.util.NutMap; +import org.nutz.repo.Base64; public class Request { public static enum METHOD { - GET, POST, OPTIONS, PUT, DELETE, TRACE, CONNECT, HEAD + GET, POST, OPTIONS, PUT, PATCH, DELETE, TRACE, CONNECT, HEAD } public static Request get(String url) { @@ -42,14 +44,12 @@ public static Request create(String url, METHOD method) { return create(url, method, new HashMap()); } - @SuppressWarnings("unchecked") public static Request create(String url, METHOD method, String paramsAsJson, Header header) { - return create(url, method, (Map) Json.fromJson(paramsAsJson), header); + return create(url, method, Json.fromJson(NutMap.class, paramsAsJson), header); } - @SuppressWarnings("unchecked") public static Request create(String url, METHOD method, String paramsAsJson) { - return create(url, method, (Map) Json.fromJson(paramsAsJson)); + return create(url, method, Json.fromJson(NutMap.class, paramsAsJson)); } public static Request create(String url, METHOD method, Map params) { @@ -89,7 +89,7 @@ public URL getUrl() { StringBuilder sb = new StringBuilder(url); try { if (this.isGet() && null != params && params.size() > 0) { - sb.append(url.indexOf('?') > 0 ? '&' : '?'); + sb.append(url.indexOf('?') >= 0 ? '&' : '?'); sb.append(getURLEncodedParams()); } cacheUrl = new URL(sb.toString()); @@ -113,6 +113,7 @@ public String getURLEncodedParams() { if (val == null) val = ""; Lang.each(val, new Each() { + @Override public void invoke(int index, Object ele, int length) throws ExitLoop, ContinueLoop, LoopException { if (offEncode) { @@ -267,4 +268,14 @@ public Request setMethodString(String methodString) { public String getMethodString() { return methodString; } + + public Request basicAuth(String user, String password) { + header("Authorization", + "Basic " + Base64.encodeToString((user + ":" + password).getBytes(), false)); + return this; + } + + public boolean hasInputStream() { + return inputStream != null; + } } diff --git a/src/org/nutz/http/Response.java b/src/org/nutz/http/Response.java index 91cb75ff44..1c1df0290f 100644 --- a/src/org/nutz/http/Response.java +++ b/src/org/nutz/http/Response.java @@ -1,44 +1,64 @@ package org.nutz.http; -import java.io.BufferedInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.io.Writer; -import java.net.HttpURLConnection; -import java.nio.charset.Charset; -import java.util.Map; - import org.nutz.lang.Encoding; import org.nutz.lang.Lang; import org.nutz.lang.Streams; import org.nutz.lang.Strings; +import org.nutz.lang.util.NutMap; + +import java.io.*; +import java.net.HttpURLConnection; +import java.nio.charset.Charset; +import java.util.Map; public class Response { private static final String DEF_PROTOCAL_VERSION = "HTTP/1.1"; + + public Response() { + } public Response(HttpURLConnection conn, Map reHeader) throws IOException { status = conn.getResponseCode(); detail = conn.getResponseMessage(); this.header = Header.create(reHeader); - String s = header.get("Set-Cookie"); - if (null != s) { - this.cookie = new Cookie(); - this.cookie.afterResponse(null, conn, null); // 解决多个Set-Cookie丢失的问题 - } + for (String name : header.keys()) + if ("Set-Cookie".equalsIgnoreCase(name)) { + this.cookie = new Cookie(); + this.cookie.afterResponse(null, conn, null); // 解决多个Set-Cookie丢失的问题 + break; + } + encode = getEncodeType(); + } + + public Response(HttpURLConnection conn, NutMap reHeader) throws IOException { + status = conn.getResponseCode(); + detail = conn.getResponseMessage(); + this.header = Header.create(reHeader); + for (String name : header.keys()) + if ("Set-Cookie".equalsIgnoreCase(name)) { + this.cookie = new Cookie(); + this.cookie.afterResponse(null, conn, null); // 解决多个Set-Cookie丢失的问题 + break; + } + encode = getEncodeType(); } private Header header; private InputStream stream; private Cookie cookie; - private String protocal = DEF_PROTOCAL_VERSION; + private String protocol = DEF_PROTOCAL_VERSION; private int status; private String detail; private String content; + private String encode; + public String getProtocol() { + return protocol; + } + + @Deprecated public String getProtocal() { - return protocal; + return protocol; } public int getStatus() { @@ -73,17 +93,29 @@ public Header getHeader() { * 根据Http头的Content-Type获取网页的编码类型,如果没有设的话则返回null */ public String getEncodeType() { - String contextType = header.get("Content-Type"); - if (null != contextType) { - for (String tmp : contextType.split(";")) { + String contentType = header.get("Content-Type"); + if (null != contentType) { + for (String tmp : contentType.split(";")) { if (tmp == null) continue; tmp = tmp.trim(); - if (tmp.startsWith("charset=")) - return Strings.trim(tmp.substring(8)).trim(); + if (tmp.startsWith("charset=")) { + tmp = Strings.trim(tmp.substring(8)).trim(); + if (tmp.contains(",")) + tmp = tmp.substring(0, tmp.indexOf(',')).trim(); + return tmp; + } } } - return null; + return Encoding.UTF8; + } + + public void setEncode(String encode) { + this.encode = encode; + } + + public String getEncode() { + return encode; } public InputStream getStream() { @@ -99,9 +131,20 @@ public Reader getReader() { } public Reader getReader(String charsetName) { + if (content != null) + return new StringReader(charsetName); return new InputStreamReader(getStream(), Charset.forName(charsetName)); } + public Reader getReader(Charset charset) { + + if (charset == null) { + throw new IllegalArgumentException("charset can not be null"); + } + + + return getReader(charset.name()); + } public Cookie getCookie() { return cookie; } @@ -139,15 +182,16 @@ public void print(Writer writer, String charsetName) { } public String getContent() { - if (Strings.isBlank(content)) { - content = getContent(null); - } - return content; + return getContent(encode); } public String getContent(String charsetName) { - if (charsetName == null) - return Streams.readAndClose(getReader()); - return Streams.readAndClose(getReader(charsetName)); + if (content == null) { + if (charsetName == null) + content = Streams.readAndClose(getReader(encode)); + else + content = Streams.readAndClose(getReader(charsetName)); + } + return content; } } diff --git a/src/org/nutz/http/Sender.java b/src/org/nutz/http/Sender.java index 3b31d3eae0..53332408f0 100644 --- a/src/org/nutz/http/Sender.java +++ b/src/org/nutz/http/Sender.java @@ -1,7 +1,6 @@ package org.nutz.http; import java.io.BufferedInputStream; -import java.io.File; import java.io.FilterOutputStream; import java.io.IOException; import java.io.InputStream; @@ -10,10 +9,7 @@ import java.net.Proxy; import java.net.Socket; import java.net.URL; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.Map.Entry; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -22,15 +18,17 @@ import java.util.zip.Inflater; import java.util.zip.InflaterInputStream; +import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLSocketFactory; -import org.nutz.http.sender.FilePostSender; -import org.nutz.http.sender.GetSender; -import org.nutz.http.sender.PostSender; +import org.nutz.http.Request.METHOD; +import org.nutz.http.sender.DefaultSenderFactory; import org.nutz.lang.Lang; +import org.nutz.lang.Strings; import org.nutz.lang.stream.VoidInputStream; import org.nutz.lang.util.Callback; +import org.nutz.lang.util.NutMap; import org.nutz.log.Log; import org.nutz.log.Logs; @@ -60,16 +58,7 @@ public static Sender create(String url, int timeout) { } public static Sender create(Request request) { - if (request.isGet() || request.isDelete()) - return new GetSender(request); - if ((request.isPost() || request.isPut()) && request.getParams() != null) { - for (Object val : request.getParams().values()) { - if (val instanceof File || val instanceof File[]) { - return new FilePostSender(request); - } - } - } - return new PostSender(request); + return factory.create(request); } public static Sender create(Request request, int timeout) { @@ -77,45 +66,43 @@ public static Sender create(Request request, int timeout) { } protected Request request; - + private int connTimeout; private int timeout; protected HttpURLConnection conn; - + protected HttpReqRespInterceptor interceptor = new Cookie(); - + protected Callback callback; - + protected boolean followRedirects = true; - + protected SSLSocketFactory sslSocketFactory; + protected HostnameVerifier hostnameVerifier; + + protected Proxy proxy; + protected Sender(Request request) { this.request = request; } - + protected Callback progressListener; public abstract Response send() throws HttpException; - protected Response createResponse(Map reHeaders) throws IOException { + protected Response createResponse(NutMap reHeaders) throws IOException { Response rep = null; if (reHeaders != null) { rep = new Response(conn, reHeaders); + String encoding = conn.getContentEncoding(); if (rep.isOK()) { InputStream is1 = conn.getInputStream(); InputStream is2 = null; - String encoding = conn.getContentEncoding(); // 如果采用了压缩,则需要处理否则都是乱码 - if (encoding != null && encoding.contains("gzip")) { - is2 = new GZIPInputStream(is1); - } else if (encoding != null && encoding.contains("deflate")) { - is2 = new InflaterInputStream(is1, new Inflater(true)); - } else { - is2 = is1; - } + is2 = detectStreamEncode(encoding, is1); BufferedInputStream is = new BufferedInputStream(is2); rep.setStream(is); @@ -123,11 +110,11 @@ protected Response createResponse(Map reHeaders) throws IOExcept else { try { - rep.setStream(conn.getInputStream()); + rep.setStream(detectStreamEncode(encoding, conn.getInputStream())); } catch (IOException e) { try { - rep.setStream(conn.getErrorStream()); + rep.setStream(detectStreamEncode(encoding, conn.getErrorStream())); } catch (Exception e1) { rep.setStream(new VoidInputStream()); @@ -135,21 +122,29 @@ protected Response createResponse(Map reHeaders) throws IOExcept } } } - if (this.interceptor != null) + if (this.interceptor != null) { this.interceptor.afterResponse(request, conn, rep); + } return rep; } - protected Map getResponseHeader() throws IOException { - if (conn.getResponseCode() < 0) + protected InputStream detectStreamEncode(String encoding, InputStream ins) throws IOException { + if (encoding != null && encoding.contains("gzip")) { + return new GZIPInputStream(ins); + } else if (encoding != null && encoding.contains("deflate")) { + return new InflaterInputStream(ins, new Inflater(true)); + } else { + return ins; + } + } + + protected NutMap getResponseHeader() throws IOException { + if (conn.getResponseCode() < 0) { throw new IOException("Network error!! resp code=" + conn.getResponseCode()); - Map reHeaders = new HashMap(); - for (Entry> en : conn.getHeaderFields().entrySet()) { - List val = en.getValue(); - if (null != val && val.size() > 0) - reHeaders.put(en.getKey(), en.getValue().get(0)); } - return reHeaders; + NutMap re = new NutMap(); + re.putAll(conn.getHeaderFields()); + return re; } protected void setupDoInputOutputFlag() { @@ -158,38 +153,41 @@ protected void setupDoInputOutputFlag() { } protected void openConnection() throws IOException { - if (this.interceptor != null) + if (this.interceptor != null) { this.interceptor.beforeConnect(request); - ProxySwitcher proxySwitcher = Http.proxySwitcher; + } + Proxy proxy = this.proxy; + if (proxy == null && Http.proxySwitcher != null) { + proxy = Http.proxySwitcher.getProxy(request); + } int connTime = connTimeout > 0 ? connTimeout : Default_Conn_Timeout; - if (proxySwitcher != null) { + if (proxy != null) { try { - Proxy proxy = proxySwitcher.getProxy(request); - if (proxy != null) { - if (Http.autoSwitch) { - Socket socket = null; - try { - socket = new Socket(); - socket.connect(proxy.address(), connTime); //5 * 1000 - OutputStream out = socket.getOutputStream(); - out.write('\n'); - out.flush(); - } - finally { - if (socket != null) - socket.close(); + if (Http.autoSwitch) { + Socket socket = null; + try { + socket = new Socket(); + socket.connect(proxy.address(), connTime); // 5 * 1000 + OutputStream out = socket.getOutputStream(); + out.write('\n'); + out.flush(); + } + finally { + if (socket != null) { + socket.close(); } } - log.debug("connect via proxy : " + proxy + " for " + request.getUrl()); - conn = (HttpURLConnection) request.getUrl().openConnection(proxy); - conn.setConnectTimeout(connTime); - conn.setInstanceFollowRedirects(followRedirects); - if (timeout > 0) - conn.setReadTimeout(timeout); - else - conn.setReadTimeout(Default_Read_Timeout); - return; } + log.debug("connect via proxy : " + proxy + " for " + request.getUrl()); + conn = (HttpURLConnection) request.getUrl().openConnection(proxy); + conn.setConnectTimeout(connTime); + conn.setInstanceFollowRedirects(followRedirects); + if (timeout > 0) { + conn.setReadTimeout(timeout); + } else { + conn.setReadTimeout(Default_Read_Timeout); + } + return; } catch (IOException e) { if (!Http.autoSwitch) { @@ -202,35 +200,59 @@ protected void openConnection() throws IOException { String host = url.getHost(); conn = (HttpURLConnection) url.openConnection(); if (conn instanceof HttpsURLConnection) { - if (sslSocketFactory != null) - ((HttpsURLConnection)conn).setSSLSocketFactory(sslSocketFactory); - else if (Http.sslSocketFactory != null) - ((HttpsURLConnection)conn).setSSLSocketFactory(Http.sslSocketFactory); + HttpsURLConnection httpsc = (HttpsURLConnection) conn; + if (sslSocketFactory != null) { + httpsc.setSSLSocketFactory(sslSocketFactory); + } else if (Http.sslSocketFactory != null) { + httpsc.setSSLSocketFactory(Http.sslSocketFactory); + } + if (hostnameVerifier != null) { + httpsc.setHostnameVerifier(hostnameVerifier); + } else if (Http.hostnameVerifier != null) { + httpsc.setHostnameVerifier(Http.hostnameVerifier); + } } if (!Lang.isIPv4Address(host)) { - if (url.getPort() > 0 && url.getPort() != 80) + if (url.getPort() > 0 && url.getPort() != 80) { host += ":" + url.getPort(); + } conn.addRequestProperty("Host", host); } conn.setConnectTimeout(connTime); - if (request.getMethodString() == null) - conn.setRequestMethod(request.getMethod().name()); - else - conn.setRequestMethod(request.getMethodString()); - if (timeout > 0) + if (request.getMethod() == METHOD.PATCH) { + conn.setRequestProperty("X-HTTP-Method-Override", "PATCH"); + conn.setRequestMethod("POST"); + } else { + if (request.getMethodString() == null) { + conn.setRequestMethod(request.getMethod().name()); + } else { + conn.setRequestMethod(request.getMethodString()); + } + } + if (timeout > 0) { conn.setReadTimeout(timeout); - else + } else { conn.setReadTimeout(Default_Read_Timeout); + } conn.setInstanceFollowRedirects(followRedirects); - if (interceptor != null) + if (interceptor != null) { this.interceptor.afterConnect(request, conn); + } } protected void setupRequestHeader() { Header header = request.getHeader(); - if (null != header) - for (Entry entry : header.getAll()) - conn.addRequestProperty(entry.getKey(), entry.getValue()); + if (null != header) { + for (String name : header.keys()) { + List values = header.getValues(name); + for (String value : values) { + if (Strings.isBlank(value)) { + continue; + } + conn.addRequestProperty(name, value); + } + } + } } public Sender setTimeout(int timeout) { @@ -241,12 +263,12 @@ public Sender setTimeout(int timeout) { public int getTimeout() { return timeout; } - + public Sender setConnTimeout(int connTimeout) { this.connTimeout = connTimeout; return this; } - + public int getConnTimeout() { return connTimeout; } @@ -255,60 +277,70 @@ public Sender setInterceptor(HttpReqRespInterceptor interceptor) { this.interceptor = interceptor; return this; } - + public Sender setCallback(Callback callback) { this.callback = callback; return this; } - + + @Override public Response call() throws Exception { Response resp = send(); - if (callback != null) + if (callback != null) { callback.invoke(resp); + } return resp; } - + public Future send(Callback callback) throws HttpException { - if (es == null) + if (es == null) { throw new IllegalStateException("Sender ExecutorService is null, Call setup first"); + } this.callback = callback; return es.submit(this); } - + protected static ExecutorService es; - + public static ExecutorService setup(ExecutorService es) { - if (Sender.es != null) + if (Sender.es != null) { shutdown(); - if (es == null) - es = Executors.newFixedThreadPool(64); + } + if (es == null) { + // 创建线程池 + es = Executors.newScheduledThreadPool(64); + } Sender.es = es; return es; } - + public static List shutdown() { ExecutorService _es = es; es = null; - if (_es == null) + if (_es == null) { return null; + } return _es.shutdownNow(); } - + public static ExecutorService getExecutorService() { return es; } - + public Sender setFollowRedirects(boolean followRedirects) { this.followRedirects = followRedirects; return this; } - + protected OutputStream getOutputStream() throws IOException { OutputStream out = conn.getOutputStream(); - if (progressListener == null) + if (progressListener == null) { return out; + } return new FilterOutputStream(out) { int count; + + @Override public void write(byte[] b, int off, int len) throws IOException { super.write(b, off, len); count += len; @@ -316,17 +348,33 @@ public void write(byte[] b, int off, int len) throws IOException { } }; } - + public int getEstimationSize() throws IOException { return 0; } - + public Sender setProgressListener(Callback progressListener) { this.progressListener = progressListener; return this; } - - public void setSSLSocketFactory(SSLSocketFactory sslSocketFactory) { + + public Sender setSSLSocketFactory(SSLSocketFactory sslSocketFactory) { this.sslSocketFactory = sslSocketFactory; + return this; + } + + public Sender setProxy(Proxy proxy) { + this.proxy = proxy; + return this; + } + + protected static SenderFactory factory = new DefaultSenderFactory(); + + public static void setFactory(SenderFactory factory) { + Sender.factory = factory; + } + + public void setHostnameVerifier(HostnameVerifier hostnameVerifier) { + this.hostnameVerifier = hostnameVerifier; } } diff --git a/src/org/nutz/http/SenderFactory.java b/src/org/nutz/http/SenderFactory.java new file mode 100644 index 0000000000..364ec29492 --- /dev/null +++ b/src/org/nutz/http/SenderFactory.java @@ -0,0 +1,6 @@ +package org.nutz.http; + +public interface SenderFactory { + + Sender create(Request request); +} \ No newline at end of file diff --git a/src/org/nutz/http/sender/DefaultSenderFactory.java b/src/org/nutz/http/sender/DefaultSenderFactory.java new file mode 100644 index 0000000000..1e6896d36f --- /dev/null +++ b/src/org/nutz/http/sender/DefaultSenderFactory.java @@ -0,0 +1,28 @@ +package org.nutz.http.sender; + +import java.io.File; + +import org.nutz.http.Request; +import org.nutz.http.Sender; +import org.nutz.http.SenderFactory; + +public class DefaultSenderFactory implements SenderFactory { + + public Sender create(Request request) { + if (request.isGet()) + return new GetSender(request); + if (request.isDelete()) { + if (request.getParams() != null || request.getData() != null || request.hasInputStream()) + return new PostSender(request); + return new GetSender(request); + } + if ((request.isPost() || request.isPut()) && (request.getParams() != null)) { + for (Object val : request.getParams().values()) { + if (val instanceof File || val instanceof File[]) { + return new FilePostSender(request); + } + } + } + return new PostSender(request); + } +} \ No newline at end of file diff --git a/src/org/nutz/img/Colors.java b/src/org/nutz/img/Colors.java index a0ade4beaa..9bfc41cc68 100644 --- a/src/org/nutz/img/Colors.java +++ b/src/org/nutz/img/Colors.java @@ -29,6 +29,32 @@ */ public final class Colors { + /** + * RGB: #FFF + */ + private static Pattern FFF_PATTERN = Pattern.compile("^([0-9A-F])([0-9A-F])([0-9A-F])$"); + /** + * RRGGBB: #F0F0F0 + */ + private static Pattern F0F0F0_PATTERN = Pattern.compile("^([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$"); + + /** + * ARGB: #9FE5 + */ + private static Pattern ARGB = Pattern.compile("^([0-9A-F])([0-9A-F])([0-9A-F])([0-9A-F])$"); + /** + * AARRGGBB: #88FF8899 + */ + private static Pattern AARRGGBB_PATTERN = Pattern.compile("^([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$"); + /** + * RGB值: rgb(255,33,89) + */ + private static Pattern RGB_PATTERN = Pattern.compile("^RGB\\s*[(]\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*[)]$"); + + /** + * RGBA值: rgba(6,6,6,0.9) + */ + private static Pattern RGBA_PATTERN = Pattern.compile("^RGBA\\s*[(]\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*((\\d[.])?\\d+)\\s*[)]$"); /** * @see #as(String) * @@ -47,8 +73,9 @@ public static Color fromString(String str) { * @return 颜色对象 */ public static Color as(String str) { - if (null == str) + if (null == str) { return Color.BLACK; + } // 整理一下字符串以便后面匹配分析 str = Strings.trim(str.toUpperCase()); @@ -57,11 +84,12 @@ public static Color as(String str) { str = str.substring(1); } - if (str.endsWith(";")) + if (str.endsWith(";")) { str = str.substring(0, str.length() - 1); + } // RGB: #FFF - Pattern p = Pattern.compile("^([0-9A-F])([0-9A-F])([0-9A-F])$"); + Pattern p = FFF_PATTERN; Matcher m = p.matcher(str); if (m.find()) { return new Color(parseInt(dup(m.group(1), 2), 16), @@ -70,7 +98,7 @@ public static Color as(String str) { } // RRGGBB: #F0F0F0 - p = Pattern.compile("^([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$"); + p = F0F0F0_PATTERN; m = p.matcher(str); if (m.find()) { return new Color(parseInt(m.group(1), 16), @@ -79,7 +107,7 @@ public static Color as(String str) { } // ARGB: #9FE5 - p = Pattern.compile("^([0-9A-F])([0-9A-F])([0-9A-F])([0-9A-F])$"); + p = ARGB; m = p.matcher(str); if (m.find()) { return new Color(parseInt(dup(m.group(2), 2), 16), @@ -89,7 +117,7 @@ public static Color as(String str) { } // AARRGGBB: #88FF8899 - p = Pattern.compile("^([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$"); + p = AARRGGBB_PATTERN; m = p.matcher(str); if (m.find()) { return new Color(parseInt(m.group(2), 16), @@ -99,7 +127,7 @@ public static Color as(String str) { } // RGB值: rgb(255,33,89) - p = Pattern.compile("^RGB\\s*[(]\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*[)]$"); + p = RGB_PATTERN; m = p.matcher(str); if (m.find()) { return new Color(parseInt(m.group(1), 10), @@ -119,7 +147,7 @@ public static Color as(String str) { // } // RGBA值: rgba(6,6,6,0.9) - p = Pattern.compile("^RGBA\\s*[(]\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*((\\d[.])?\\d+)\\s*[)]$"); + p = RGBA_PATTERN; m = p.matcher(str); if (m.find()) { float alpha = Float.parseFloat(m.group(4)); diff --git a/src/org/nutz/img/Images.java b/src/org/nutz/img/Images.java index 7941dc5b80..5a77c3b2fd 100644 --- a/src/org/nutz/img/Images.java +++ b/src/org/nutz/img/Images.java @@ -83,8 +83,9 @@ public static BufferedImage rotate(Object srcIm, File taIm, int degree) { public static BufferedImage rotate(String srcPath, String taPath, int degree) throws IOException { File srcIm = Files.findFile(srcPath); - if (null == srcIm) + if (null == srcIm) { throw Lang.makeThrow("Fail to find image file '%s'!", srcPath); + } File taIm = Files.createFileIfNoExists(taPath); return rotate(srcIm, taIm, degree); @@ -107,8 +108,9 @@ public static BufferedImage rotate(BufferedImage image, int degree) { int x = 0; int y = 0; degree = degree % 360; - if (degree < 0) + if (degree < 0) { degree = 360 + degree;// 将角度转换到0-360度之间 + } double ang = degree * 0.0174532925;// 将角度转为弧度 /** @@ -202,8 +204,9 @@ public static BufferedImage zoomScale(String srcPath, Color bgColor) throws IOException { File srcIm = Files.findFile(srcPath); - if (null == srcIm) + if (null == srcIm) { throw Lang.makeThrow("Fail to find image file '%s'!", srcPath); + } File taIm = Files.createFileIfNoExists(taPath); return zoomScale(srcIm, taIm, w, h, bgColor); @@ -226,7 +229,7 @@ public static BufferedImage zoomScale(String srcPath, */ public static BufferedImage zoomScale(BufferedImage im, int w, int h, Color bgColor) { if (w == -1 || h == -1) { - return zoomScale(im, w, h); + return scale(im, w, h); } // 检查背景颜色 @@ -264,7 +267,7 @@ else if (oR < nR) { } // 创建图像 - BufferedImage re = new BufferedImage(w, h, im.getType()); + BufferedImage re = new BufferedImage(w, h, im.getType() == 0 ? BufferedImage.TYPE_3BYTE_BGR : im.getType()); Graphics2D gc = re.createGraphics(); if (null != bgColor) { gc.setColor(bgColor); @@ -376,8 +379,9 @@ public static BufferedImage clipScale(Object srcIm, File taIm, int w, int h) public static BufferedImage clipScale(String srcPath, String taPath, int w, int h) throws IOException { File srcIm = Files.findFile(srcPath); - if (null == srcIm) + if (null == srcIm) { throw Lang.makeThrow("Fail to find image file '%s'!", srcPath); + } File taIm = Files.createFileIfNoExists(taPath); return clipScale(srcIm, taIm, w, h); @@ -448,8 +452,9 @@ public static BufferedImage clipScale(String srcPath, int[] endPoint) throws IOException { File srcIm = Files.findFile(srcPath); - if (null == srcIm) + if (null == srcIm) { throw Lang.makeThrow("Fail to find image file '%s'!", srcPath); + } File taIm = Files.createFileIfNoExists(taPath); return clipScale(srcIm, taIm, startPoint, endPoint); @@ -682,42 +687,43 @@ public static BufferedImage addWatermark(Object srcIm, int px = 0; int py = 0; switch (pos) { - case WATERMARK_TOP_LEFT: - px = margin; - py = margin; - break; - case WATERMARK_TOP_CENTER: - px = (cw - mw) / 2; - py = margin; - break; - case WATERMARK_TOP_RIGHT: - px = cw - mw - margin; - py = margin; - break; - case WATERMARK_CENTER_LEFT: - px = margin; - py = (ch - mh) / 2; - break; - case WATERMARK_CENTER: - px = (cw - mw) / 2; - py = (ch - mh) / 2; - break; - case WATERMARK_CENTER_RIGHT: - px = cw - mw - margin; - py = (ch - mh) / 2; - break; - case WATERMARK_BOTTOM_LEFT: - px = margin; - py = ch - mh - margin; - break; - case WATERMARK_BOTTOM_CENTER: - px = (cw - mw) / 2; - py = ch - mh - margin; - break; - case WATERMARK_BOTTOM_RIGHT: - px = cw - mw - margin; - py = ch - mh - margin; - break; + case WATERMARK_TOP_LEFT: + px = margin; + py = margin; + break; + case WATERMARK_TOP_CENTER: + px = (cw - mw) / 2; + py = margin; + break; + case WATERMARK_TOP_RIGHT: + px = cw - mw - margin; + py = margin; + break; + case WATERMARK_CENTER_LEFT: + px = margin; + py = (ch - mh) / 2; + break; + case WATERMARK_CENTER: + px = (cw - mw) / 2; + py = (ch - mh) / 2; + break; + case WATERMARK_CENTER_RIGHT: + px = cw - mw - margin; + py = (ch - mh) / 2; + break; + case WATERMARK_BOTTOM_LEFT: + px = margin; + py = ch - mh - margin; + break; + case WATERMARK_BOTTOM_CENTER: + px = (cw - mw) / 2; + py = ch - mh - margin; + break; + case WATERMARK_BOTTOM_RIGHT: + px = cw - mw - margin; + py = ch - mh - margin; + break; + default: } // 添加水印 @@ -961,11 +967,17 @@ public static BufferedImage read(Object img) { if (img instanceof CharSequence) { return ImageIO.read(Files.checkFile(img.toString())); } - if (img instanceof File) + if (img instanceof File) { return ImageIO.read((File) img); + } + + if(img instanceof byte[]) { + return ImageIO.read(new ByteArrayInputStream((byte[])img)); + } - if (img instanceof URL) + if (img instanceof URL) { img = ((URL) img).openStream(); + } if (img instanceof InputStream) { File tmp = File.createTempFile("nutz_img", ".jpg"); @@ -982,14 +994,16 @@ public static BufferedImage read(Object img) { catch (IOException e) { try { InputStream in = null; - if (img instanceof File) + if (img instanceof File) { in = new FileInputStream((File) img); - else if (img instanceof URL) + } else if (img instanceof URL) { in = ((URL) img).openStream(); - else if (img instanceof InputStream) + } else if (img instanceof InputStream) { in = (InputStream) img; - if (in != null) + } + if (in != null) { return readJpeg(in); + } } catch (IOException e2) { e2.fillInStackTrace(); @@ -1103,8 +1117,9 @@ private static BufferedImage readJpeg(InputStream in) throws IOException { break; } } - if (reader == null) + if (reader == null) { return null; + } try { ImageInputStream input = ImageIO.createImageInputStream(in); reader.setInput(input); diff --git a/src/org/nutz/ioc/Ioc.java b/src/org/nutz/ioc/Ioc.java index 82e2f21ad7..0f339510e9 100644 --- a/src/org/nutz/ioc/Ioc.java +++ b/src/org/nutz/ioc/Ioc.java @@ -1,5 +1,7 @@ package org.nutz.ioc; +import java.lang.annotation.Annotation; + /** * Ioc 容器接口 * @@ -60,5 +62,11 @@ public interface Ioc { String[] getNamesByType(Class klass); + String[] getNamesByAnnotation(Class klass); + K getByType(Class klass); + + Ioc addBean(String name, Object obj); + + Class getType(String name) throws ObjectLoadException; } diff --git a/src/org/nutz/ioc/Ioc2.java b/src/org/nutz/ioc/Ioc2.java index 48d58a8f02..aea62f55db 100644 --- a/src/org/nutz/ioc/Ioc2.java +++ b/src/org/nutz/ioc/Ioc2.java @@ -1,5 +1,7 @@ package org.nutz.ioc; +import java.lang.annotation.Annotation; + /** * 容器更高级的方法 * @@ -32,7 +34,7 @@ public interface Ioc2 extends Ioc { IocContext getIocContext(); /** - * 增加 ValuePfoxyMaker + * 增加 ValueProxyMaker * * @see org.nutz.ioc.ValueProxy * @see org.nutz.ioc.ValueProxyMaker @@ -42,4 +44,8 @@ public interface Ioc2 extends Ioc { String[] getNamesByType(Class klass, IocContext context); T getByType(Class type, IocContext context); + + String[] getNamesByAnnotation(Class klass, IocContext context); + + Class getType(String beanName, IocContext context) throws ObjectLoadException; } diff --git a/src/org/nutz/ioc/IocEventListener.java b/src/org/nutz/ioc/IocEventListener.java new file mode 100644 index 0000000000..bf0c419ac8 --- /dev/null +++ b/src/org/nutz/ioc/IocEventListener.java @@ -0,0 +1,29 @@ +package org.nutz.ioc; + +public interface IocEventListener { + + /** + * 对象已创建后,属性未注入 + * @param obj 新建好的对象 + * @param beanName 对象的名称 + * @return 可以是新对象,也可以是被替换的新对象 + */ + Object afterBorn(Object obj, String beanName); + + /** + * 对象已创建,属性已经注入,准备返回给调用方法 + * @param obj 新建好的对象 + * @param beanName 对象的名称 + * @return 可以是新对象,也可以是被替换的新对象 + */ + Object afterCreate(Object obj, String beanName); + +// /** +// * 对象调用depose方法后 +// * @param obj 新建好的对象 +// * @param beanName 对象的名称 +// */ +// void afterDepose(Object obj, String beanName); + + int getOrder(); +} diff --git a/src/org/nutz/ioc/IocLoading.java b/src/org/nutz/ioc/IocLoading.java index 3c9798731c..e6a5912ed5 100644 --- a/src/org/nutz/ioc/IocLoading.java +++ b/src/org/nutz/ioc/IocLoading.java @@ -42,10 +42,6 @@ public IocObject map2iobj(Map map) throws ObjectLoadException { ifld.setValue(object2value(en.getValue())); iobj.addField(ifld); } -// if (log.isWarnEnabled()) // TODO 移除这种兼容性 -// log.warn("Using *Declared* ioc-define (without type or events)!!! Pls use Standard Ioc-Define!!" -// + " Bean will define as:\n" -// + Json.toJson(iobj)); } else { Object v = map.get("type"); // type diff --git a/src/org/nutz/ioc/IocMaking.java b/src/org/nutz/ioc/IocMaking.java index cf7c938f19..dfd515a9cf 100644 --- a/src/org/nutz/ioc/IocMaking.java +++ b/src/org/nutz/ioc/IocMaking.java @@ -20,6 +20,8 @@ public class IocMaking { private List vpms; private MirrorFactory mirrors; + + private List listeners; public IocMaking( Ioc ioc, MirrorFactory mirrors, @@ -55,8 +57,17 @@ public MirrorFactory getMirrors() { return mirrors; } + public List getListeners() { + return listeners; + } + public IocMaking clone(String objectName) { - return new IocMaking(ioc, mirrors, context, objectMaker, vpms, objectName); + return new IocMaking(ioc, mirrors, context, objectMaker, vpms, objectName).setListeners(listeners); + } + + public IocMaking setListeners(List listeners) { + this.listeners = listeners; + return this; } public ValueProxy makeValue(IocValue iv) { @@ -70,5 +81,4 @@ public ValueProxy makeValue(IocValue iv) { Json.toJson(iv.getValue()), objectName); } - } diff --git a/src/org/nutz/ioc/Iocs.java b/src/org/nutz/ioc/Iocs.java index 3dcc38d564..5042c2346e 100644 --- a/src/org/nutz/ioc/Iocs.java +++ b/src/org/nutz/ioc/Iocs.java @@ -11,6 +11,7 @@ import org.nutz.lang.Lang; import org.nutz.lang.Strings; import org.nutz.lang.meta.Pair; +import org.nutz.lang.util.Regex; /** * @author zozoh(zozohtnt@gmail.com) @@ -23,7 +24,7 @@ public abstract class Iocs { public static boolean isIocObject(Map map) { for (Entry en : map.entrySet()) - if (!en.getKey().matches(OBJFIELDS)) + if (!Regex.match(OBJFIELDS, en.getKey())) return false; return true; } @@ -55,7 +56,7 @@ public static IocObject mergeWith(IocObject me, IocObject it) { if (me.getType() == null) me.setType(it.getType()); - // don't need merge signleon + // don't need merge singleton // merge events if (me.getEvents() == null) { @@ -125,7 +126,8 @@ public static Object self(Object obj) { public static IocObject wrap(Object obj) { IocObject iobj = new IocObject(); - //iobj.setType(obj.getClass()); + if (obj != null) + iobj.setType(obj.getClass()); iobj.setFactory(Iocs.class.getName() + "#self"); IocValue ival = new IocValue(null, new StaticValue(obj)); iobj.addArg(ival); diff --git a/src/org/nutz/ioc/aop/impl/DefaultMirrorFactory.java b/src/org/nutz/ioc/aop/impl/DefaultMirrorFactory.java index 0c4c6ccb93..756f8c34cb 100644 --- a/src/org/nutz/ioc/aop/impl/DefaultMirrorFactory.java +++ b/src/org/nutz/ioc/aop/impl/DefaultMirrorFactory.java @@ -10,6 +10,7 @@ import org.nutz.aop.DefaultClassDefiner; import org.nutz.aop.MethodInterceptor; import org.nutz.aop.asm.AsmClassAgent; +import org.nutz.conf.NutConf; import org.nutz.ioc.Ioc; import org.nutz.ioc.aop.MirrorFactory; import org.nutz.ioc.aop.config.AopConfigration; @@ -17,6 +18,7 @@ import org.nutz.ioc.aop.config.impl.AnnotationAopConfigration; import org.nutz.ioc.aop.config.impl.ComboAopConfigration; import org.nutz.lang.Mirror; +import org.nutz.lang.random.R; import org.nutz.lang.util.AbstractLifeCycle; import org.nutz.log.Log; import org.nutz.log.Logs; @@ -40,9 +42,13 @@ public class DefaultMirrorFactory extends AbstractLifeCycle implements MirrorFac private static final Object lock = new Object(); public ThreadLocal L = new ThreadLocal(); + + private String id; public DefaultMirrorFactory(Ioc ioc) { this.ioc = ioc; + if (NutConf.AOP_USE_CLASS_ID) + id = R.UU32().substring(4); } public Mirror getMirror(Class type, String name) { @@ -81,7 +87,8 @@ public Mirror getMirror(Class type, String name) { if (cd == null) { cd = DefaultClassDefiner.defaultOne(); } - ClassAgent agent = new AsmClassAgent(); + AsmClassAgent agent = new AsmClassAgent(); + agent.id = id; for (InterceptorPair interceptorPair : interceptorPairs) agent.addInterceptor(interceptorPair.getMethodMatcher(), interceptorPair.getMethodInterceptor()); diff --git a/src/org/nutz/ioc/impl/NutIoc.java b/src/org/nutz/ioc/impl/NutIoc.java index 2fb0c1e024..317a9f77f0 100644 --- a/src/org/nutz/ioc/impl/NutIoc.java +++ b/src/org/nutz/ioc/impl/NutIoc.java @@ -1,19 +1,25 @@ package org.nutz.ioc.impl; +import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; import java.util.Date; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; +import org.nutz.ioc.Ioc; import org.nutz.ioc.Ioc2; import org.nutz.ioc.IocContext; +import org.nutz.ioc.IocEventListener; import org.nutz.ioc.IocException; import org.nutz.ioc.IocLoader; import org.nutz.ioc.IocLoading; import org.nutz.ioc.IocMaking; +import org.nutz.ioc.ObjectLoadException; import org.nutz.ioc.ObjectMaker; import org.nutz.ioc.ObjectProxy; import org.nutz.ioc.ValueProxyMaker; @@ -77,6 +83,10 @@ public class NutIoc implements Ioc2 { * */ private Set supportedTypes; + + protected List listeners; + + protected ThreadLocal listenerH = new ThreadLocal(); public NutIoc(IocLoader loader) { this(loader, new ScopeContext(DEF_SCOPE), DEF_SCOPE); @@ -95,7 +105,6 @@ protected NutIoc(ObjectMaker maker, IocContext context, String defaultScope, MirrorFactory mirrors) { - log.info("NutIoc init begin ..."); this.createTime = new Date(); this.maker = maker; this.defaultScope = defaultScope; @@ -124,7 +133,7 @@ protected NutIoc(ObjectMaker maker, /** * @return 一个新创建的 IocLoading 对象 */ - private IocLoading createLoading() { + protected IocLoading createLoading() { if (null == supportedTypes) { synchronized (this) { if (null == supportedTypes) { @@ -205,7 +214,6 @@ public T get(Class type, String name, IocContext context) throws IocExcep throw new IocException(name, "NULL TYPE object '%s'", name); else iobj.setType(type); - // 检查对象级别 if (Strings.isBlank(iobj.getScope())) iobj.setScope(defaultScope); @@ -213,7 +221,25 @@ public T get(Class type, String name, IocContext context) throws IocExcep // 根据对象定义,创建对象,maker 会自动的缓存对象到 context 中 if (log.isDebugEnabled()) log.debugf("\t >> Make...'%s'<%s>", name, type == null ? "" : type); - op = maker.make(ing, iobj); + if (iobj.getType() != null && IocEventListener.class.isAssignableFrom(iobj.getType())) { + if (listenerH.get() != null) { + op = maker.make(ing, iobj); + } + else { + try { + listenerH.set(Boolean.TRUE); + op = maker.make(ing, iobj); + } + finally { + listenerH.remove(); + } + } + } + else { + _checkIocEventListeners(); + ing.setListeners(listeners); + op = maker.make(ing, iobj); + } } // 处理异常 catch (IocException e) { @@ -269,6 +295,7 @@ public void depose() { log.warn("something happen when depose IocLoader", e); } context.depose(); + loader.clear(); deposed = true; if (log.isInfoEnabled()) log.infof("%s@%s is deposed. startup date [%s]", @@ -291,6 +318,8 @@ public String[] getNames() { public void addValueProxyMaker(ValueProxyMaker vpm) { vpms.add(0, vpm);// 优先使用最后加入的ValueProxyMaker + supportedTypes = null; + loader.clear(); } public IocContext getIocContext() { @@ -327,7 +356,7 @@ public IocMaking makeIocMaking(IocContext context, String name) { @Override public String toString() { - return "/*NutIoc*/\n{\nloader:" + loader + ",\n}"; + return "/*NutIoc count=" + loader.getName().length + "*/"; } @Override @@ -348,17 +377,7 @@ public String[] getNamesByType(Class klass) { } public String[] getNamesByType(Class klass, IocContext context) { - List names = new ArrayList(); - for (String name : getNames()) { - try { - IocObject iobj = loader.load(createLoading(), name); - if (iobj != null - && iobj.getType() != null - && klass.isAssignableFrom(iobj.getType())) - names.add(name); - } - catch (Exception e) {} - } + List names = new ArrayList(loader.getNamesByTypes(createLoading(), klass)); IocContext cntx; if (null == context || context == this.context) cntx = this.context; @@ -369,7 +388,38 @@ public String[] getNamesByType(Class klass, IocContext context) { if (op.getObj() != null && klass.isAssignableFrom(op.getObj().getClass())) names.add(name); } - return new LinkedHashSet(names).toArray(new String[names.size()]); + LinkedHashSet re = new LinkedHashSet(); + for (String name : names) { + if (Strings.isBlank(name) || "null".equals(name)) + continue; + re.add(name); + } + return re.toArray(new String[re.size()]); + } + + public String[] getNamesByAnnotation(Class klass) { + return this.getNamesByAnnotation(klass, null); + } + + public String[] getNamesByAnnotation(Class klass, IocContext context) { + List names = new ArrayList(loader.getNamesByAnnotation(createLoading(), klass)); + IocContext cntx; + if (null == context || context == this.context) + cntx = this.context; + else + cntx = new ComboContext(context, this.context); + for (String name : cntx.names()) { + ObjectProxy op = cntx.fetch(name); + if (op.getObj() != null && klass.getAnnotation(klass) != null) + names.add(name); + } + LinkedHashSet re = new LinkedHashSet(); + for (String name : names) { + if (Strings.isBlank(name) || "null".equals(name)) + continue; + re.add(name); + } + return re.toArray(new String[re.size()]); } public K getByType(Class klass) { @@ -410,4 +460,51 @@ public K getByType(Class klass, IocContext context) { + klass.getName(), "none ioc bean match class=" + klass.getName()); } + + protected void _checkIocEventListeners() { + if (listeners != null) + return; + List listeners = new ArrayList(); + for (String beanName : this.loader.getNamesByTypes(createLoading(), IocEventListener.class)) { + listeners.add(get(IocEventListener.class, beanName)); + } + if (listeners.size() > 0) { + Collections.sort(listeners, new Comparator() { + public int compare(IocEventListener prev, IocEventListener next) { + if (prev.getOrder() == next.getOrder()) + return 0; + return prev.getOrder() > next.getOrder() ? -1 : 1; + } + }); + } + this.listeners = listeners; + } + + public Ioc addBean(String name, Object obj) { + if (obj == null) + throw new RuntimeException("can't add bean=null!!"); + if (Strings.isBlank(name)) + throw new RuntimeException("can't add bean name is blank!!"); + if (obj instanceof ObjectProxy) + getIocContext().save("app", name, (ObjectProxy)obj); + else + getIocContext().save("app", name, new ObjectProxy(obj)); + return this; + } + + public Class getType(String beanName) throws ObjectLoadException { + return getType(beanName, null); + } + + public Class getType(String beanName, IocContext context) throws ObjectLoadException { + IocContext cntx; + if (null == context || context == this.context) + cntx = this.context; + else + cntx = new ComboContext(context, this.context); + ObjectProxy op = cntx.fetch(beanName); + if (op != null && op.getObj() != null) + return op.getObj().getClass(); + return loader.getType(createLoading(), beanName); + } } diff --git a/src/org/nutz/ioc/impl/ObjectMakerImpl.java b/src/org/nutz/ioc/impl/ObjectMakerImpl.java index 3fe6df845a..fd3a2910cb 100644 --- a/src/org/nutz/ioc/impl/ObjectMakerImpl.java +++ b/src/org/nutz/ioc/impl/ObjectMakerImpl.java @@ -13,7 +13,6 @@ import org.nutz.ioc.meta.IocEventSet; import org.nutz.ioc.meta.IocField; import org.nutz.ioc.meta.IocObject; -import org.nutz.ioc.trigger.MethodEventTrigger; import org.nutz.ioc.weaver.DefaultWeaver; import org.nutz.ioc.weaver.FieldInjector; import org.nutz.lang.Lang; @@ -22,6 +21,8 @@ import org.nutz.lang.born.Borning; import org.nutz.lang.born.MethodBorning; import org.nutz.lang.born.MethodCastingBorning; +import org.nutz.lang.reflect.FastClassFactory; +import org.nutz.lang.reflect.FastMethod; /** * 在这里,需要考虑 AOP @@ -47,6 +48,7 @@ public ObjectProxy make(final IocMaking ing, IocObject iobj) { try { // 准备对象的编织方式 DefaultWeaver dw = new DefaultWeaver(); + dw.setListeners(ing.getListeners()); op.setWeaver(dw); // 构造函数参数 @@ -136,8 +138,9 @@ public Object born(Object... args) { dw.fill(ing, obj); // 对象创建完毕,如果有 create 事件,调用它 - dw.onCreate(obj); - + Object tmp = dw.onCreate(obj); + if (tmp != null) + op.setObj(tmp); } catch (IocException e) { ing.getContext().remove(iobj.getScope(), ing.getObjectName()); @@ -155,8 +158,7 @@ public Object born(Object... args) { } @SuppressWarnings({"unchecked"}) - private static IocEventTrigger createTrigger(Mirror mirror, - String str) { + private static IocEventTrigger createTrigger(Mirror mirror, final String str) { if (Strings.isBlank(str)) return null; if (str.contains(".")) { @@ -168,12 +170,20 @@ private static IocEventTrigger createTrigger(Mirror mirror, throw Lang.wrapThrow(e); } } - try { - return new MethodEventTrigger(mirror.findMethod(str)); - } - catch (NoSuchMethodException e) { - throw Lang.wrapThrow(e); - } + return new IocEventTrigger() { + protected FastMethod fm; + public void trigger(Object obj) { + try { + if (fm == null) { + Method method = Mirror.me(obj).findMethod(str); + fm = FastClassFactory.get(method); + } + fm.invoke(obj); + } catch (Exception e) { + throw Lang.wrapThrow(e); + } + } + }; } } diff --git a/src/org/nutz/ioc/impl/PropertiesProxy.java b/src/org/nutz/ioc/impl/PropertiesProxy.java index 6e874f9c55..f34caed15e 100644 --- a/src/org/nutz/ioc/impl/PropertiesProxy.java +++ b/src/org/nutz/ioc/impl/PropertiesProxy.java @@ -1,46 +1,32 @@ package org.nutz.ioc.impl; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileFilter; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.util.ArrayList; -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Properties; - import org.nutz.castor.Castors; -import org.nutz.lang.Files; -import org.nutz.lang.Lang; -import org.nutz.lang.Mirror; -import org.nutz.lang.Streams; -import org.nutz.lang.Strings; +import org.nutz.lang.*; import org.nutz.lang.inject.Injecting; import org.nutz.lang.util.Disks; import org.nutz.lang.util.FileVisitor; import org.nutz.lang.util.MultiLineProperties; import org.nutz.log.Log; import org.nutz.log.Logs; +import org.nutz.mapl.Mapl; import org.nutz.resource.NutResource; import org.nutz.resource.Scans; +import java.io.*; +import java.util.*; + /** * 代理Properties文件,以便直接在Ioc配置文件中使用 - * + * * @author wendal(wendal1985@gmail.com) * @author zozoh(zozohtnt@gmail.com) - * + * * @since 1.b.37 */ public class PropertiesProxy extends MultiLineProperties { private static final Log log = Logs.get(); - + private static final String VM_NUTZ_CONF_PATH = "nutz.conf.path."; // 是否为UTF8格式的Properties文件 private final boolean utf8; // 是否忽略无法加载的文件 @@ -49,7 +35,9 @@ public class PropertiesProxy extends MultiLineProperties { public PropertiesProxy() { this(true); } - + + private Integer keyIndex; + public PropertiesProxy(boolean utf8, String... paths) { this(utf8); this.setPaths(paths); @@ -95,11 +83,16 @@ public PropertiesProxy(Reader r) { } } + public void setKeyIndex(Integer keyIndex) { + this.keyIndex = keyIndex; + } + /** * 加载指定文件/文件夹的Properties文件,合并成一个Properties对象 *

* 如果有重复的key,请务必注意加载的顺序!! - * + * + * * @param paths * 需要加载的Properties文件路径 */ @@ -139,15 +132,42 @@ public void setPaths(String... paths) { } } + public List getKeysWithPrefix(String prefix) { + List list = new ArrayList(); + for (String key : getKeys()) { + if (key != null && key.startsWith(prefix)) { + list.add(key); + } + } + return list; + } + /** * 加载指定文件/文件夹的Properties文件 - * + * * @param paths * 需要加载的Properties文件路径 * @return 加载到的Properties文件Resource列表 */ private List getResources(String... paths) { List list = new ArrayList(); + if (null != keyIndex) { + try { + String vmJsonStr = ""; + Properties p = System.getProperties(); + for (Object key : p.keySet()) { + if (((String) key).startsWith(VM_NUTZ_CONF_PATH + keyIndex)) + vmJsonStr = p.getProperty((String) key).trim(); + } + if (Strings.isNotBlank(vmJsonStr)) { + paths = vmJsonStr.split("\\,"); + } + } catch (Exception e) { + if (log.isDebugEnabled()) { + log.debug("-D" + VM_NUTZ_CONF_PATH + keyIndex + " value is invalid: " + e.getMessage()); + } + } + } for (String path : paths) { try { List resources = Scans.me().loadResource("^.+[.]properties$", path); @@ -222,7 +242,7 @@ public List getList(String key,String separatorChar) { } return re; } - + public String trim(String key) { return Strings.trim(get(key)); } @@ -283,7 +303,7 @@ public Properties toProperties() { * 根据自身的一个键对应的值扩展自身的属性。 *

* 本函数假设你可能有下面的键值: - * + * *

      * ...
      * files:
@@ -291,12 +311,12 @@ public Properties toProperties() {
      * path/to_b.properties
      * #End files
      * 
- * + * * 那么如果你调用 joinByKey("files");
* 则会将其值的两个属性文件展开,加入到自身。 *

* 属性文件的路径可以是磁盘全路径,或者在 CLASSPATH 里的路径 - * + * * @param key * 键 * @return 自身 @@ -336,7 +356,7 @@ else if (f.isFile()) { /** * 将另外一个 Properties 文本加入本散列表 - * + * * @param r * 文本输入流 * @return 自身 @@ -359,22 +379,35 @@ public PropertiesProxy joinAndClose(Reader r) { public Map toMap() { return new LinkedHashMap(this); } - + public String get(String key) { return super.get(key); } - - @SuppressWarnings({"unchecked", "rawtypes"}) - public T make(Class klass, String prefix) { - Mirror mirror = Mirror.me(klass); - T t = mirror.born(); - Map map = toMap(); - map = Lang.filter(map, prefix, null, null, null); - for (Entry en : ((Map)map).entrySet()) { - String name = en.getKey(); - Injecting setter = mirror.getInjecting(name); - setter.inject(t, en.getValue()); - } - return t; - } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public T makeDeep(Class klass, String prefix) { + Map map = this; + return (T) Mapl.maplistToObj(Lang.filter(map, prefix, null, null, null), klass); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + public T make(Class klass, String prefix) { + Map map = this; + Mirror mirror = Mirror.me(klass); + T t = mirror.born(); + map = Lang.filter(map, prefix, null, null, null); + for (Entry en : ((Map) map).entrySet()) { + String name = en.getKey(); + Injecting setter = null; + try { + setter = mirror.getInjecting(name); + } + catch (Exception e) { + log.debugf("no such field(name=%s) at object class=%s, skip", name, t.getClass().getName()); + continue; + } + setter.inject(t, en.getValue()); + } + return t; + } } diff --git a/src/org/nutz/ioc/impl/ScopeContext.java b/src/org/nutz/ioc/impl/ScopeContext.java index ec136fd6b1..afa67c6917 100644 --- a/src/org/nutz/ioc/impl/ScopeContext.java +++ b/src/org/nutz/ioc/impl/ScopeContext.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -127,6 +128,10 @@ public void depose() { } public Set names() { - return objs.keySet(); + if (objs == null) + return Collections.emptySet(); + synchronized (this) { + return new HashSet(objs.keySet()); + } } } diff --git a/src/org/nutz/ioc/java/ChainParsing.java b/src/org/nutz/ioc/java/ChainParsing.java index aa6db5d929..9ee2734d89 100644 --- a/src/org/nutz/ioc/java/ChainParsing.java +++ b/src/org/nutz/ioc/java/ChainParsing.java @@ -4,6 +4,7 @@ import org.nutz.lang.Strings; import org.nutz.lang.util.LinkedArray; import org.nutz.lang.util.LinkedCharArray; +import org.nutz.lang.util.Regex; public class ChainParsing { @@ -54,8 +55,9 @@ else if (c == '\'' || c == '"') { clearStringBuffer(); for (i++; i < cs.length; i++) { char n = cs[i]; - if (n == c) + if (n == c) { break; + } sb.append(n); } addNode(new StringNode(clearStringBuffer())); @@ -137,10 +139,12 @@ private boolean hasFieldOrFunction() { int dot = 0, comma = 0; for (int currentIndex = i; currentIndex < cs.length; currentIndex++) { char c = cs[currentIndex]; - if (c == '.') + if (c == '.') { dot = currentIndex; - if (c == ',') + } + if (c == ',') { comma = currentIndex; + } } return dot < comma || (dot != 0 && comma == 0);//点号在逗号前边或后边有点号没有逗号 } @@ -150,22 +154,23 @@ private void checkIfNeedAddNode() { if (!Strings.isBlank(sb)) { String s = Strings.trim(clearStringBuffer()); // null - if (s.equalsIgnoreCase("null")) { + if ("null".equalsIgnoreCase(s)) { addNode(new NullNode()); } // boolean - else if (s.matches("^(true|false)$")) { + else if (Regex.match("^(true|false)$", s)) { addNode(new BooleanNode(s)); } // number - else if (s.matches("^([-]?[0-9]+)?([.][0-9]+)?([fL]?)$")) { + else if (Regex.match("^([-]?[0-9]+)?([.][0-9]+)?([fL]?)$", s)) { addNode(new NumberNode(s)); } // the chain is empty else if (null == last) { int pos = s.lastIndexOf('.'); - if (pos < 0) + if (pos < 0) { throw Lang.makeThrow("Don't know how to invoke '%s'", s); + } String className = s.substring(0, pos); String funcName = s.substring(pos + 1); addNode(new StaticFunctionNode(className, @@ -182,8 +187,9 @@ else if (null == last) { private String readToDot() { for (i++; i < cs.length; i++) { char c = cs[i]; - if (c == '.' || c == ',') + if (c == '.' || c == ',') { break; + } sb.append(c); } return clearStringBuffer(); @@ -192,8 +198,9 @@ private String readToDot() { private String readToComma() { for (i++; i < cs.length; i++) { char c = cs[i]; - if (c == ',' || c == ')') + if (c == ',' || c == ')') { break; + } sb.append(c); } return clearStringBuffer(); diff --git a/src/org/nutz/ioc/loader/annotation/AnnotationIocLoader.java b/src/org/nutz/ioc/loader/annotation/AnnotationIocLoader.java index 424ac02569..bb90f7fa29 100644 --- a/src/org/nutz/ioc/loader/annotation/AnnotationIocLoader.java +++ b/src/org/nutz/ioc/loader/annotation/AnnotationIocLoader.java @@ -1,5 +1,6 @@ package org.nutz.ioc.loader.annotation; +import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; @@ -8,12 +9,7 @@ import java.util.HashMap; import java.util.List; -import org.nutz.castor.Castors; -import org.nutz.ioc.IocException; -import org.nutz.ioc.IocLoader; -import org.nutz.ioc.IocLoading; -import org.nutz.ioc.Iocs; -import org.nutz.ioc.ObjectLoadException; +import org.nutz.ioc.*; import org.nutz.ioc.annotation.InjectName; import org.nutz.ioc.meta.IocEventSet; import org.nutz.ioc.meta.IocField; @@ -47,19 +43,14 @@ public AnnotationIocLoader() { } public AnnotationIocLoader(String... packages) { - for (String packageZ : packages) { - for (Class classZ : Scans.me().scanPackage(packageZ)) + for (String pkg : packages) { + log.infof(" > scan '%s'", pkg); + for (Class classZ : Scans.me().scanPackage(pkg)) { addClass(classZ); + } } - if (map.size() > 0) { - if (log.isInfoEnabled()) - log.infof("Found %s classes in %s base-packages!\nbeans = %s", - map.size(), - packages.length, - Castors.me().castToString(map.keySet())); - } else { - log.warn("NONE Annotation-Class found!! Check your ioc configure!! packages=" - + Arrays.toString(packages)); + if (map.isEmpty()) { + log.warnf("NONE @IocBean found!! Check your ioc configure!! packages=%s", Arrays.toString(packages)); } this.packages = packages; } @@ -69,16 +60,15 @@ public void addClass(Class classZ) { || classZ.isMemberClass() || classZ.isEnum() || classZ.isAnnotation() - || classZ.isAnonymousClass()) + || classZ.isAnonymousClass()) { return; + } int modify = classZ.getModifiers(); - if (Modifier.isAbstract(modify) || (!Modifier.isPublic(modify))) + if (Modifier.isAbstract(modify) || (!Modifier.isPublic(modify))) { return; + } IocBean iocBean = classZ.getAnnotation(IocBean.class); if (iocBean != null) { - if (log.isDebugEnabled()) - log.debugf("Found @IocBean : %s", classZ); - // 采用 @IocBean->name String beanName = iocBean.name(); if (Strings.isBlank(beanName)) { @@ -94,38 +84,47 @@ public void addClass(Class classZ) { } // 重名了, 需要用户用@IocBean(name="xxxx") 区分一下 - if (map.containsKey(beanName)) - throw new IocException(beanName, + if (map.containsKey(beanName)) { + throw new IocException(beanName, "Duplicate beanName=%s, by %s !! Have been define by %s !!", beanName, classZ.getName(), map.get(beanName).getType().getName()); + } IocObject iocObject = new IocObject(); iocObject.setType(classZ); map.put(beanName, iocObject); + + log.infof(" > add '%-40s' - %s", beanName, classZ.getName()); iocObject.setSingleton(iocBean.singleton()); - if (!Strings.isBlank(iocBean.scope())) + if (!Strings.isBlank(iocBean.scope())) { iocObject.setScope(iocBean.scope()); + } // 看看构造函数都需要什么函数 String[] args = iocBean.args(); // if (null == args || args.length == 0) // args = iocBean.param(); - if (null != args && args.length > 0) - for (String value : args) + if (null != args && args.length > 0) { + for (String value : args) { iocObject.addArg(Iocs.convert(value, true)); + } + } // 设置Events IocEventSet eventSet = new IocEventSet(); iocObject.setEvents(eventSet); - if (!Strings.isBlank(iocBean.create())) + if (!Strings.isBlank(iocBean.create())) { eventSet.setCreate(iocBean.create().trim().intern()); - if (!Strings.isBlank(iocBean.depose())) + } + if (!Strings.isBlank(iocBean.depose())) { eventSet.setDepose(iocBean.depose().trim().intern()); - if (!Strings.isBlank(iocBean.fetch())) + } + if (!Strings.isBlank(iocBean.fetch())) { eventSet.setFetch(iocBean.fetch().trim().intern()); + } // 处理字段(以@Inject方式,位于字段) List fieldList = new ArrayList(); @@ -140,16 +139,48 @@ public void addClass(Class classZ) { iocField.setName(field.getName()); IocValue iocValue; if (Strings.isBlank(inject.value())) { - iocValue = new IocValue(); - iocValue.setType(IocValue.TYPE_REFER_TYPE); - iocValue.setValue(field); - } else + if ("ioc".equals(field.getName())) { + iocValue = new IocValue(); + iocValue.setType(IocValue.TYPE_REFER); + iocValue.setValue("$ioc"); + } + else { + iocValue = new IocValue(); + iocValue.setType(IocValue.TYPE_REFER_TYPE); + iocValue.setValue(field); + } + } else { iocValue = Iocs.convert(inject.value(), true); + } iocField.setValue(iocValue); iocField.setOptional(inject.optional()); iocObject.addField(iocField); fieldList.add(iocField.getName()); } + // 处理字段(以@Value方式,位于字段) + Field[] valueFields = mirror.getFields(Value.class); + for (Field valueField : valueFields) { + Value value = valueField.getAnnotation(Value.class); + String configName; + if(Strings.isBlank(value.name())) { + configName = Strings.lowerFirst(valueField.getName()); + } else { + configName = value.name(); + } + String defaultValue = value.defaultValue(); + IocValue iocValue; + if(Strings.isBlank(defaultValue)) { + iocValue = Iocs.convert(String.format("java:$conf.get('%s')", configName), true); + } else { + iocValue = Iocs.convert(String.format("java:$conf.get('%s', '%s')", configName, defaultValue), true); + } + IocField iocField = new IocField(); + iocField.setName(valueField.getName()); + iocField.setValue(iocValue); + iocField.setOptional(false); + iocObject.addField(iocField); + fieldList.add(iocField.getName()); + } // 处理字段(以@Inject方式,位于set方法) Method[] methods; try { @@ -170,28 +201,33 @@ public void addClass(Class classZ) { } for (Method method : methods) { Inject inject = method.getAnnotation(Inject.class); - if (inject == null) + if (inject == null) { continue; + } // 过滤特殊方法 int m = method.getModifiers(); - if (Modifier.isAbstract(m) || (!Modifier.isPublic(m)) || Modifier.isStatic(m)) + if (Modifier.isAbstract(m) || (!Modifier.isPublic(m)) || Modifier.isStatic(m)) { continue; + } String methodName = method.getName(); if (methodName.startsWith("set") && methodName.length() > 3 && method.getParameterTypes().length == 1) { IocField iocField = new IocField(); iocField.setName(Strings.lowerFirst(methodName.substring(3))); - if (fieldList.contains(iocField.getName())) + if (fieldList.contains(iocField.getName())) { throw duplicateField(beanName, classZ, iocField.getName()); + } IocValue iocValue; if (Strings.isBlank(inject.value())) { iocValue = new IocValue(); iocValue.setType(IocValue.TYPE_REFER_TYPE); iocValue.setValue(Strings.lowerFirst(methodName.substring(3)) + "#" + method.getParameterTypes()[0].getName()); - } else + } else { iocValue = Iocs.convert(inject.value(), true); + } iocField.setValue(iocValue); + iocField.setOptional(inject.optional()); //对setter方法上的@Inject注解,也支持optional设置 iocObject.addField(iocField); fieldList.add(iocField.getName()); } @@ -200,16 +236,25 @@ public void addClass(Class classZ) { String[] flds = iocBean.fields(); if (flds != null && flds.length > 0) { for (String fieldInfo : flds) { - if (fieldList.contains(fieldInfo)) + if (fieldList.contains(fieldInfo)) { throw duplicateField(beanName, classZ, fieldInfo); + } IocField iocField = new IocField(); if (fieldInfo.contains(":")) { // dao:jndi:dataSource/jdbc形式 String[] datas = fieldInfo.split(":", 2); // 完整形式, 与@Inject完全一致了 iocField.setName(datas[0]); + if (datas[1].endsWith("!optional")) { //fields中支持optional设置 + iocField.setOptional(true); + datas[1] = datas[1].substring(0, datas[1].indexOf("!optional")); + } iocField.setValue(Iocs.convert(datas[1], true)); iocObject.addField(iocField); } else { + if (fieldInfo.endsWith("!optional")) { + iocField.setOptional(true); + fieldInfo = fieldInfo.substring(0, fieldInfo.indexOf("!optional")); + } // 基本形式, 引用与自身同名的bean iocField.setName(fieldInfo); IocValue iocValue = new IocValue(); @@ -230,8 +275,9 @@ public void addClass(Class classZ) { // 看看有没有方法标注了@IocBean for (Method method : methods) { IocBean ib = method.getAnnotation(IocBean.class); - if (ib == null) + if (ib == null) { continue; + } handleIocBeanMethod(method, ib, beanName); } } else { @@ -250,19 +296,29 @@ protected void handleIocBeanMethod(Method method, IocBean ib, String facotryBean } beanName = Strings.lowerFirst(methodName); } - if (log.isDebugEnabled()) + if (log.isDebugEnabled()) { log.debugf("Found @IocBean method : %s define as name=%s", Lang.simpleMethodDesc(method), beanName); + } IocObject iobj = new IocObject(); iobj.setType(method.getReturnType()); iobj.setFactory("$"+facotryBeanName+"#"+method.getName()); List paramNames = MethodParamNamesScaner.getParamNames(method); Class[] paramTypes = method.getParameterTypes(); + Annotation[][] anns = method.getParameterAnnotations(); for (int i = 0; i < paramTypes.length; i++) { Class paramType = paramTypes[i]; String paramName = (paramNames != null && (paramNames.size() >= (i - 1))) ? paramNames.get(i) : "arg" + i; IocValue ival = new IocValue(); - Inject inject = paramType.getAnnotation(Inject.class); + Inject inject = null; + if (anns[i] != null && anns[i].length > 0) { + for (Annotation anno : anns[i]) { + if (anno instanceof Inject) { + inject = (Inject)anno; + break; + } + } + } if (inject == null || Strings.isBlank(inject.value())) { ival.setType(IocValue.TYPE_REFER_TYPE); ival.setValue(paramName + "#" + paramType.getName()); @@ -274,26 +330,34 @@ protected void handleIocBeanMethod(Method method, IocBean ib, String facotryBean // 设置Events IocEventSet eventSet = new IocEventSet(); iobj.setEvents(eventSet); - if (!Strings.isBlank(ib.create())) + if (!Strings.isBlank(ib.create())) { eventSet.setCreate(ib.create().trim().intern()); - if (!Strings.isBlank(ib.depose())) + } + if (!Strings.isBlank(ib.depose())) { eventSet.setDepose(ib.depose().trim().intern()); - if (!Strings.isBlank(ib.fetch())) + } + if (!Strings.isBlank(ib.fetch())) { eventSet.setFetch(ib.fetch().trim().intern()); + } + iobj.setSingleton(ib.singleton()); map.put(beanName, iobj); } + @Override public String[] getName() { return map.keySet().toArray(new String[map.size()]); } + @Override public boolean has(String name) { return map.containsKey(name); } + @Override public IocObject load(IocLoading loading, String name) throws ObjectLoadException { - if (has(name)) + if (has(name)) { return map.get(name); + } throw new ObjectLoadException("Object '" + name + "' without define! Pls check your ioc configure"); } @@ -304,6 +368,7 @@ private static final IocException duplicateField(String beanName, Class class name); } + @Override public String toString() { return "/*AnnotationIocLoader*/\n" + Json.toJson(map); } diff --git a/src/org/nutz/ioc/loader/annotation/Value.java b/src/org/nutz/ioc/loader/annotation/Value.java new file mode 100644 index 0000000000..65459a8bca --- /dev/null +++ b/src/org/nutz/ioc/loader/annotation/Value.java @@ -0,0 +1,26 @@ +package org.nutz.ioc.loader.annotation; + +import java.lang.annotation.*; + +/** + * @author wentao + * @title + * @description + * @create 2021-03-09 2:46 下午 + */ +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Value { + /** + * 获取配置项,默认使用字段名获取 配置实例 @Value(name="server.port") 如不配置name,则使用字段名首字母小写作为key获取配置 + * @return + */ + String name() default ""; + + /** + * 配置项默认值 + * @return + */ + String defaultValue() default ""; +} diff --git a/src/org/nutz/ioc/loader/combo/ComboIocLoader.java b/src/org/nutz/ioc/loader/combo/ComboIocLoader.java index bb640e328e..2fe1adfb69 100644 --- a/src/org/nutz/ioc/loader/combo/ComboIocLoader.java +++ b/src/org/nutz/ioc/loader/combo/ComboIocLoader.java @@ -1,5 +1,6 @@ package org.nutz.ioc.loader.combo; +import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -23,6 +24,7 @@ import org.nutz.lang.Mirror; import org.nutz.lang.Strings; import org.nutz.lang.util.AbstractLifeCycle; +import org.nutz.lang.util.Callback; import org.nutz.lang.util.LifeCycle; import org.nutz.log.Log; import org.nutz.log.Logs; @@ -38,6 +40,8 @@ public class ComboIocLoader extends AbstractLifeCycle implements IocLoader { private static final Log log = Logs.get(); private List iocLoaders = new ArrayList(); + + protected Map iobjs = new HashMap(); /** * 这个构造方法需要一组特殊的参数 @@ -143,42 +147,96 @@ public boolean has(String name) { } public IocObject load(IocLoading loading, String name) throws ObjectLoadException { - - for (IocLoader iocLoader : iocLoaders) - if (iocLoader.has(name)) { - IocObject iocObject = iocLoader.load(loading, name); - if (log.isDebugEnabled()) { - // TODO 弄成更好看的格式,方便debug - String printName; - if (iocLoader instanceof AnnotationIocLoader) { - String packages = Arrays.toString(((AnnotationIocLoader)iocLoader).getPackages()); - printName = "AnnotationIocLoader(packages="+packages+")"; - } else if (JsonLoader.class.equals(iocLoader.getClass()) - && ((JsonLoader)iocLoader).getPaths() != null) { - String paths = Arrays.toString(((JsonLoader)iocLoader).getPaths()); - printName = "JsonLoader(paths="+paths+")"; - } else { - printName = iocLoader.getClass().getSimpleName() + "@" + iocLoader.hashCode(); - } - log.debugf("Found IocObject(%s) in %s", - name, printName); - } + for (IocLoader loader : iocLoaders) + if (loader.has(name)) { + IocObject iocObject = loader.load(loading, name); + printFoundIocBean(name, loader); + iobjs.put(name, iocObject); return iocObject; } throw new ObjectLoadException("Object '" + name + "' without define!"); } + + public Set getNamesByTypes(IocLoading loading, Class klass) { + Set names = new HashSet(); + for (IocLoader loader : iocLoaders) { + for (String name : loader.getName()) { + if (names.contains(name)) + continue; + try { + IocObject iobj = loader.load(loading, name); + if (iobj.getType() != null && klass.isAssignableFrom(iobj.getType())) + names.add(name); + } + catch (ObjectLoadException e) { + // nop + } + } + } + return names; + } + + public Set getNamesByAnnotation(IocLoading loading, Class klass) { + Set names = new HashSet(); + for (IocLoader loader : iocLoaders) { + for (String name : loader.getName()) { + if (names.contains(name)) + continue; + try { + IocObject iobj = loader.load(loading, name); + if (iobj.getType() != null && iobj.getType().getAnnotation(klass) != null) + names.add(name); + } + catch (ObjectLoadException e) { + // nop + } + } + } + return names; + } + + public void each(IocLoading loading, Callback callback) throws ObjectLoadException { + for (IocLoader loader : iocLoaders) { + for (String name : loader.getName()) { + callback.invoke(loader.load(loading, name)); + } + } + } public void addLoader(IocLoader loader) { if (null != loader) { if (iocLoaders.contains(loader)) return; iocLoaders.add(loader); - if (log.isInfoEnabled()) - log.infof("add loader : %s : \n - %s", - loader.getClass(), - Lang.concat("\n - ", loader.getName())); } } + + protected void printFoundIocBean(String name, IocLoader loader) { + if (log.isDebugEnabled()) { + String printName; + if (loader instanceof AnnotationIocLoader) { + String packages = Arrays.toString(((AnnotationIocLoader)loader).getPackages()); + printName = "AnnotationIocLoader(packages="+packages+")"; + } else if (loader instanceof JsonLoader && ((JsonLoader)loader).getPaths() != null) { + String paths = Arrays.toString(((JsonLoader)loader).getPaths()); + printName = "JsonLoader(paths="+paths+")"; + } else { + printName = loader.getClass().getSimpleName() + "@" + loader.hashCode(); + } + log.debugf("Found IocObject(%s) in %s", name, printName); + } + } + + public Class getType(IocLoading loading, String beanName) throws ObjectLoadException { + for (IocLoader loader : iocLoaders) { + if (loader.has(beanName)) { + IocObject iobj = loader.load(loading, beanName); + if (iobj.getType() != null) + return iobj.getType(); + } + } + return null; + } /** * 类别名 @@ -214,4 +272,8 @@ public void depose() throws Exception { ((LifeCycle) loader).depose(); } } + + public void clear() { + iobjs.clear(); + } } diff --git a/src/org/nutz/ioc/loader/json/JsonLoader.java b/src/org/nutz/ioc/loader/json/JsonLoader.java index cc32eea8f2..186e42baca 100644 --- a/src/org/nutz/ioc/loader/json/JsonLoader.java +++ b/src/org/nutz/ioc/loader/json/JsonLoader.java @@ -58,7 +58,7 @@ public JsonLoader(String... paths) { this.paths = paths; } - private void loadFromReader(Reader reader) { + protected void loadFromReader(Reader reader) { String s = Lang.readAll(reader); Map> map = (Map>) Json.fromJson(s); if (null != map && map.size() > 0) diff --git a/src/org/nutz/ioc/loader/map/MapLoader.java b/src/org/nutz/ioc/loader/map/MapLoader.java index 561686b8f7..cf4eb9f958 100644 --- a/src/org/nutz/ioc/loader/map/MapLoader.java +++ b/src/org/nutz/ioc/loader/map/MapLoader.java @@ -12,8 +12,6 @@ import org.nutz.ioc.meta.IocObject; import org.nutz.json.Json; import org.nutz.lang.Lang; -import org.nutz.log.Log; -import org.nutz.log.Logs; /** * 从一个 Map 对象中读取配置信息,支持 Parent @@ -22,8 +20,6 @@ * @author wendal(wendal1985@gmail.com) */ public class MapLoader implements IocLoader { - - private static final Log log = Logs.get(); protected Map> map; @@ -63,8 +59,6 @@ public IocObject load(IocLoading loading, String name) throws ObjectLoadExceptio Map m = getMap(name); if (null == m) throw new ObjectLoadException("Object '" + name + "' without define!"); - if(log.isDebugEnabled()) - log.debug("Loading define for name="+name); // If has parent Object p = m.get("parent"); if (null != p) { diff --git a/src/org/nutz/ioc/loader/properties/PropertiesIocLoader.java b/src/org/nutz/ioc/loader/properties/PropertiesIocLoader.java index b70ca111d6..912032c7e4 100644 --- a/src/org/nutz/ioc/loader/properties/PropertiesIocLoader.java +++ b/src/org/nutz/ioc/loader/properties/PropertiesIocLoader.java @@ -30,16 +30,19 @@ public PropertiesIocLoader(String...paths) { log.debug("beans = " + objs.keySet()); } + @Override public String[] getName() { reload(); return objs.keySet().toArray(new String[objs.size()]); } + @Override public IocObject load(IocLoading loading, String name) throws ObjectLoadException { reload(); return objs.get(name); } + @Override public boolean has(String name) { reload(); return objs.containsKey(name); @@ -49,11 +52,12 @@ public boolean has(String name) { public void reload() { List beanNames = new ArrayList(); for (String key : keys()) { - if (!key.startsWith("ioc.") || key.length() < 5) + if (!key.startsWith("ioc.") || key.length() < 5) { continue; + } String[] tmp = key.split("[.]"); if (tmp.length == 3) { - if (tmp[2].equals("type") || tmp[2].equals("factory")) { + if ("type".equals(tmp[2]) || "factory".equals(tmp[2])) { beanNames.add(tmp[1]); } } diff --git a/src/org/nutz/ioc/weaver/DefaultWeaver.java b/src/org/nutz/ioc/weaver/DefaultWeaver.java index 8d2ae5410c..4759046d5f 100644 --- a/src/org/nutz/ioc/weaver/DefaultWeaver.java +++ b/src/org/nutz/ioc/weaver/DefaultWeaver.java @@ -1,5 +1,8 @@ package org.nutz.ioc.weaver; +import java.util.List; + +import org.nutz.ioc.IocEventListener; import org.nutz.ioc.IocEventTrigger; import org.nutz.ioc.IocMaking; import org.nutz.ioc.ObjectWeaver; @@ -33,6 +36,10 @@ public class DefaultWeaver implements ObjectWeaver { * 字段注入器列表 */ private FieldInjector[] fields; + + protected List listeners; + + protected String beanName; public void setCreate(IocEventTrigger create) { this.create = create; @@ -49,6 +56,10 @@ public void setArgs(ValueProxy[] args) { public void setFields(FieldInjector[] fields) { this.fields = fields; } + + public void setListeners(List listeners) { + this.listeners = listeners; + } public T fill(IocMaking ing, T obj) { // 设置字段的值 @@ -64,12 +75,31 @@ public Object born(IocMaking ing) { args[i] = this.args[i].get(ing); // 创建实例 - return borning.born(args); + Object obj = borning.born(args); + if (shallTrigger(obj)) { + for (IocEventListener listener : listeners) { + obj = listener.afterBorn(obj, beanName); + } + } + return obj; } public Object onCreate(Object obj) { if (null != create && null != obj) create.trigger(obj); + if (shallTrigger(obj)) { + for (IocEventListener listener : listeners) { + obj = listener.afterCreate(obj, beanName); + } + } return obj; } + + protected boolean shallTrigger(Object obj) { + return obj != null && listeners != null && !(obj instanceof IocEventListener) && listeners.size() > 0; + } + + public void setBeanName(String beanName) { + this.beanName = beanName; + } } diff --git a/src/org/nutz/json/AbstractJsonEntityFieldMaker.java b/src/org/nutz/json/AbstractJsonEntityFieldMaker.java index cfff9f45b5..3111bd035c 100644 --- a/src/org/nutz/json/AbstractJsonEntityFieldMaker.java +++ b/src/org/nutz/json/AbstractJsonEntityFieldMaker.java @@ -12,7 +12,7 @@ public abstract class AbstractJsonEntityFieldMaker implements JsonEntityFieldMak @Override public List make(Mirror mirror) { - Field[] flds = mirror.getFields(); + Field[] flds = mirror.getFields(true, false); List fields = new ArrayList(flds.length); for (Field fld : flds) { JsonEntityField ef = make(mirror, fld); diff --git a/src/org/nutz/json/Json.java b/src/org/nutz/json/Json.java index eee8604578..ba73ae7d3a 100644 --- a/src/org/nutz/json/Json.java +++ b/src/org/nutz/json/Json.java @@ -9,11 +9,26 @@ import java.io.Reader; import java.io.Writer; import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.nutz.json.entity.JsonEntity; +import org.nutz.json.handler.JsonArrayHandler; +import org.nutz.json.handler.JsonBooleanHandler; +import org.nutz.json.handler.JsonClassHandler; +import org.nutz.json.handler.JsonDateTimeHandler; +import org.nutz.json.handler.JsonEnumHandler; +import org.nutz.json.handler.JsonIterableHandler; +import org.nutz.json.handler.JsonJsonRenderHandler; +import org.nutz.json.handler.JsonLocalDateLikeHandler; +import org.nutz.json.handler.JsonMapHandler; +import org.nutz.json.handler.JsonMirrorHandler; +import org.nutz.json.handler.JsonNumberHandler; +import org.nutz.json.handler.JsonPojoHandler; +import org.nutz.json.handler.JsonStringLikeHandler; import org.nutz.json.impl.JsonEntityFieldMakerImpl; import org.nutz.json.impl.JsonRenderImpl; import org.nutz.lang.Files; @@ -441,4 +456,36 @@ public static void setDefaultFieldMaker(JsonEntityFieldMaker fieldMaker) { public static JsonEntityFieldMaker getDefaultFieldMaker() { return deftMaker; } + protected static List handlers = new ArrayList(); + public static void addTypeHandler(JsonTypeHandler handler) { + if (!handlers.contains(handler)) + handlers.add(0, handler); + } + public static List getTypeHandlers() { + return Collections.unmodifiableList(handlers); + } + + /** + * + */ + static { + handlers.add(new JsonJsonRenderHandler()); + handlers.add(new JsonClassHandler()); + handlers.add(new JsonMirrorHandler()); + handlers.add(new JsonEnumHandler()); + handlers.add(new JsonNumberHandler()); + handlers.add(new JsonBooleanHandler()); + handlers.add(new JsonStringLikeHandler()); + handlers.add(new JsonDateTimeHandler()); + try { + Class.forName("java.time.temporal.TemporalAccessor"); + handlers.add(new JsonLocalDateLikeHandler()); + } + catch (Throwable e) { + } + handlers.add(new JsonMapHandler()); + handlers.add(new JsonIterableHandler()); + handlers.add(new JsonArrayHandler()); + handlers.add(new JsonPojoHandler()); + } } \ No newline at end of file diff --git a/src/org/nutz/json/JsonException.java b/src/org/nutz/json/JsonException.java index fdd35a3afc..934eed8551 100644 --- a/src/org/nutz/json/JsonException.java +++ b/src/org/nutz/json/JsonException.java @@ -11,6 +11,13 @@ public JsonException(String msg) { super(msg); } + + public JsonException(String message, Throwable cause) { + super(message, cause); + } + + + public JsonException(int row, int col, char cursor, String message) { super(String.format("!Json syntax error nearby [row:%d,col:%d char '%c'], reason: '%s'", row, diff --git a/src/org/nutz/json/JsonField.java b/src/org/nutz/json/JsonField.java index 9f1480a0e7..139267d360 100644 --- a/src/org/nutz/json/JsonField.java +++ b/src/org/nutz/json/JsonField.java @@ -41,4 +41,8 @@ String dateFormat() default ""; String dataFormat() default ""; + + String timeZone() default ""; + + String locale() default ""; } diff --git a/src/org/nutz/json/JsonFormat.java b/src/org/nutz/json/JsonFormat.java index 03cb5b76db..ecc1896451 100644 --- a/src/org/nutz/json/JsonFormat.java +++ b/src/org/nutz/json/JsonFormat.java @@ -83,6 +83,10 @@ public JsonFormat(boolean compact) { } public static class Function { + /** + * 是否忽略 JsonShape 注解 + */ + public static String ignoreJsonShape = "ignoreJsonShape"; /** * 缩进时用的字符串 */ @@ -140,6 +144,9 @@ public static class Function { public static String nullBooleanAsFalse = "nullBooleanAsFalse"; public static String nullNumberAsZero = "nullNumberAsZero"; public static String timeZone = "timeZone"; + public static String locale = "locale"; + public static String dateFormatRaw = "dateFormatRaw"; + public static String longAsString = "longAsString"; } @JsonField(ignore = true) @@ -212,6 +219,18 @@ public JsonFormat decreaseIndent() { public String getIndentBy() { return getString(Function.indentBy, " "); } + /** + * 设置忽略 JsonShape 注解 + * @return + */ + public JsonFormat ignoreJsonShape() { + put(Function.ignoreJsonShape,true); + return this; + } + + public boolean isIgnoreJsonShape() { + return getBoolean(Function.ignoreJsonShape); + } /** * 设置Json输出格式的缩进时用的字符串 @@ -380,6 +399,7 @@ public JsonFormat setDateFormat(String df) { put(Function.dateFormat, new TimeStampDateFormat()); } else { put(Function.dateFormat, new SimpleDateFormat(df)); + put(Function.dateFormatRaw, df); } return this; } @@ -513,4 +533,26 @@ public JsonFormat setNullNumberAsZero(boolean nullNumberAsZero) { put(Function.nullNumberAsZero, nullNumberAsZero); return this; } + + public JsonFormat setLocale(String locale) { + put(Function.locale, locale); + return this; + } + + public String getLocale() { + return getString(Function.locale); + } + + public String getDateFormatRaw() { + return getString(Function.dateFormatRaw); + } + + public JsonFormat setLongAsString(boolean longAsString) { + put(Function.longAsString, longAsString); + return this; + } + + public boolean isLongAsString() { + return getBoolean(Function.longAsString, false); + } } diff --git a/src/org/nutz/json/JsonRender.java b/src/org/nutz/json/JsonRender.java index 85838435af..0e2861ed15 100644 --- a/src/org/nutz/json/JsonRender.java +++ b/src/org/nutz/json/JsonRender.java @@ -2,6 +2,11 @@ import java.io.IOException; import java.io.Writer; +import java.util.List; +import java.util.Map; + +import org.nutz.json.entity.JsonEntityField; +import org.nutz.json.impl.JsonPair; /** * 对象-->String, 一般就是写入Writer中 @@ -13,6 +18,44 @@ public interface JsonRender { void render(Object obj) throws IOException, JsonException; void setWriter(Writer writer); - + + Writer getWriter(); + void setFormat(JsonFormat jsonFormat); + + /** + * 循环依赖的检查 + */ + boolean memoContains(Object obj); + + void string2Json(String s) throws IOException; + + @SuppressWarnings("rawtypes") + void map2Json(Map map) throws IOException; + + void writeRaw(String raw) throws IOException; + + void appendBraceEnd() throws IOException; + + void appendBraceBegin() throws IOException; + + void appendPairEnd() throws IOException; + + boolean isIgnore(String name, Object value); + + void appendPair(boolean needPairEnd, String name, Object value) throws IOException; + + void appendPairSep() throws IOException; + + void appendPairBegin() throws IOException; + + void appendName(String name) throws IOException; + + void increaseFormatIndent(); + + void decreaseFormatIndent(); + + String value2string(JsonEntityField jef, Object value); + + void writeItem(List list) throws IOException; } diff --git a/src/org/nutz/json/JsonShape.java b/src/org/nutz/json/JsonShape.java index 5a004c5b19..7e46f606e9 100644 --- a/src/org/nutz/json/JsonShape.java +++ b/src/org/nutz/json/JsonShape.java @@ -11,14 +11,16 @@ * */ @Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE}) +@Target({ ElementType.TYPE }) @Documented public @interface JsonShape { - Type value() default Type.NAME; + Type value() default Type.NAME; - public static enum Type { - ORDINAL, NAME, OBJECT - } + String nameKey() default "name"; + + public static enum Type { + ORDINAL, NAME, OBJECT, OBJECTWITHNAME + } } diff --git a/src/org/nutz/json/JsonTypeHandler.java b/src/org/nutz/json/JsonTypeHandler.java new file mode 100644 index 0000000000..7d816ed936 --- /dev/null +++ b/src/org/nutz/json/JsonTypeHandler.java @@ -0,0 +1,51 @@ +package org.nutz.json; + +import java.io.IOException; + +import org.nutz.lang.Mirror; + +public abstract class JsonTypeHandler { + + /** + * 是否支持 fromJson操作 + * @param mirror TODO + * @param obj TODO + */ + public boolean supportFromJson(Mirror mirror, Object obj) { + return false; + } + + /** + * 是否支持当前对象的toJson操作 + * @param mirror obj对应的Mirrir + * @param obj 正在等着被转换的对象 + * @param jf JsonFormat实例 + * @return 若支持,接下来会调用toJson + */ + public boolean supportToJson(Mirror mirror, Object obj, JsonFormat jf) { + return false; + } + + /** + * 将对象变成json字符串 + * @param mirror currentObj对应的Mirrir + * @param currentObj 当前正在转换的对象 + * @param r Json渲染器 + * @param jf JsonFormat实例 + * @throws IOException + */ + public void toJson(Mirror mirror, Object currentObj, JsonRender r, JsonFormat jf) throws IOException { + + } + + public Object fromJson(Object obj, Mirror mirror) throws Exception { + return null; + }; + + /** + * 是否需要进行循环依赖检测 + */ + public boolean shallCheckMemo() { + return false; + } +} diff --git a/src/org/nutz/json/entity/JsonEntity.java b/src/org/nutz/json/entity/JsonEntity.java index 3787e3638c..a6c341676f 100644 --- a/src/org/nutz/json/entity/JsonEntity.java +++ b/src/org/nutz/json/entity/JsonEntity.java @@ -107,7 +107,14 @@ public boolean toJson(Object obj, JsonFormat jf, Writer writer) throws IOExcepti writer.write((String)toJsonMethod.invoke(obj, jf)); } catch (Exception e) { - throw new JsonException(err); + // born success, but toJson fail + if (err == null) { + RuntimeException cause = Lang.wrapThrow(e); + throw new JsonException(cause.getMessage(), cause); + // born fail + } else { + throw new JsonException(err); + } } return true; } diff --git a/src/org/nutz/json/entity/JsonEntityField.java b/src/org/nutz/json/entity/JsonEntityField.java index 6ee85f36ba..04930d3628 100644 --- a/src/org/nutz/json/entity/JsonEntityField.java +++ b/src/org/nutz/json/entity/JsonEntityField.java @@ -4,9 +4,12 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Type; +import java.text.DateFormat; import java.text.DecimalFormat; import java.text.Format; import java.text.SimpleDateFormat; +import java.util.Locale; +import java.util.TimeZone; import org.nutz.json.JsonField; import org.nutz.json.JsonIgnore; @@ -132,7 +135,17 @@ public static JsonEntityField eval(Mirror mirror, Field fld) { if(jfmirror.isNumber()){ jef.dataFormat = new DecimalFormat(dataFormat); }else if(jfmirror.isDateTimeLike()){ - jef.dataFormat = new SimpleDateFormat(dataFormat); + DateFormat df = null; + if (Strings.isBlank(jf.locale())) { + df = new SimpleDateFormat(dataFormat); + } + else { + df = new SimpleDateFormat(dataFormat, Locale.forLanguageTag(jf.locale())); + } + if (!Strings.isBlank(jf.timeZone())) { + df.setTimeZone(TimeZone.getTimeZone(jf.timeZone())); + } + jef.dataFormat = df; } } } @@ -197,4 +210,20 @@ public Mirror getMirror() { public void setGenericType(Type genericType) { this.genericType = ReflectTool.getInheritGenericType(declaringClass, genericType);; } + + public void setInjecting(Injecting injecting) { + this.injecting = injecting; + } + + public void setEjecting(Ejecting ejecting) { + this.ejecting = ejecting; + } + + public Ejecting getEjecting() { + return ejecting; + } + + public Injecting getInjecting() { + return injecting; + } } \ No newline at end of file diff --git a/src/org/nutz/json/handler/JsonArrayHandler.java b/src/org/nutz/json/handler/JsonArrayHandler.java new file mode 100644 index 0000000000..3d693963a6 --- /dev/null +++ b/src/org/nutz/json/handler/JsonArrayHandler.java @@ -0,0 +1,56 @@ +package org.nutz.json.handler; + +import java.io.IOException; +import java.io.Writer; +import java.lang.reflect.Array; + +import org.nutz.castor.Castors; +import org.nutz.json.JsonFormat; +import org.nutz.json.JsonRender; +import org.nutz.json.JsonTypeHandler; +import org.nutz.lang.Mirror; + +/** + * + * @author wendal + * + */ +public class JsonArrayHandler extends JsonTypeHandler { + + @Override + public boolean supportFromJson(Mirror mirror, Object obj) { + return mirror.isArray(); + } + + @Override + public boolean supportToJson(Mirror mirror, Object obj, JsonFormat jf) { + return mirror.isArray(); + } + + @Override + public void toJson(Mirror mirror, Object currentObj, JsonRender r, JsonFormat jf) throws IOException { + Writer writer = r.getWriter(); + writer.append('['); + int len = Array.getLength(currentObj) - 1; + if (len > -1) { + int i; + for (i = 0; i < len; i++) { + r.render(Array.get(currentObj, i)); + r.appendPairEnd(); + writer.append(' '); + } + r.render(Array.get(currentObj, i)); + } + writer.append(']'); + } + + @Override + public Object fromJson(Object obj, Mirror mirror) throws Exception { + return Castors.me().castTo(obj, mirror.getType()); + } + + @Override + public boolean shallCheckMemo() { + return true; + } +} diff --git a/src/org/nutz/json/handler/JsonBooleanHandler.java b/src/org/nutz/json/handler/JsonBooleanHandler.java new file mode 100644 index 0000000000..2ea682c932 --- /dev/null +++ b/src/org/nutz/json/handler/JsonBooleanHandler.java @@ -0,0 +1,33 @@ +package org.nutz.json.handler; + +import java.io.IOException; + +import org.nutz.castor.Castors; +import org.nutz.json.JsonFormat; +import org.nutz.json.JsonRender; +import org.nutz.json.JsonTypeHandler; +import org.nutz.lang.Mirror; + +/** + * + * @author wendal + * + */ +public class JsonBooleanHandler extends JsonTypeHandler { + + public boolean supportFromJson(Mirror mirror, Object obj) { + return mirror.isBoolean(); + } + + public boolean supportToJson(Mirror mirror, Object obj, JsonFormat jf) { + return mirror.isBoolean(); + } + + public void toJson(Mirror mirror, Object currentObj, JsonRender r, JsonFormat jf) throws IOException { + r.writeRaw(String.valueOf(currentObj)); + } + + public Object fromJson(Object obj, Mirror mirror) throws Exception { + return Castors.me().castTo(obj, Boolean.class); + } +} diff --git a/src/org/nutz/json/handler/JsonClassHandler.java b/src/org/nutz/json/handler/JsonClassHandler.java new file mode 100644 index 0000000000..55f3344a7a --- /dev/null +++ b/src/org/nutz/json/handler/JsonClassHandler.java @@ -0,0 +1,34 @@ +package org.nutz.json.handler; + +import java.io.IOException; + +import org.nutz.json.JsonFormat; +import org.nutz.json.JsonRender; +import org.nutz.json.JsonTypeHandler; +import org.nutz.lang.Lang; +import org.nutz.lang.Mirror; + +/** + * + * @author wendal + * + */ +public class JsonClassHandler extends JsonTypeHandler { + + public boolean supportFromJson(Mirror mirror, Object obj) { + return mirror.getType() == Class.class; + } + + public boolean supportToJson(Mirror mirror, Object obj, JsonFormat jf) { + return obj != null && obj instanceof Class; + } + + @SuppressWarnings("rawtypes") + public void toJson(Mirror mirror, Object currentObj, JsonRender r, JsonFormat jf) throws IOException { + r.string2Json(((Class) currentObj).getName()); + } + + public Object fromJson(Object obj, Mirror mirror) throws Exception { + return Lang.loadClass(String.valueOf(obj)); + } +} diff --git a/src/org/nutz/json/handler/JsonDateTimeHandler.java b/src/org/nutz/json/handler/JsonDateTimeHandler.java new file mode 100644 index 0000000000..473ec33f45 --- /dev/null +++ b/src/org/nutz/json/handler/JsonDateTimeHandler.java @@ -0,0 +1,57 @@ +package org.nutz.json.handler; + +import java.io.IOException; +import java.text.DateFormat; +import java.util.Date; + +import org.nutz.castor.Castors; +import org.nutz.json.JsonFormat; +import org.nutz.json.JsonRender; +import org.nutz.json.JsonTypeHandler; +import org.nutz.lang.Mirror; + +/** + * + * @author wendal + * + */ +public class JsonDateTimeHandler extends JsonTypeHandler { + + public boolean supportFromJson(Mirror mirror, Object obj) { + return mirror.isDateTimeLike(); + } + + public boolean supportToJson(Mirror mirror, Object obj, JsonFormat jf) { + return mirror.isDateTimeLike(); + } + + @Override + public void toJson(Mirror mirror, Object currentObj, JsonRender r, JsonFormat jf) throws IOException { + boolean flag = true; + if (currentObj instanceof Date) { + String _val = doDateFormat(jf, (Date) currentObj, null); + if (_val != null) { + r.string2Json(_val); + flag = false; + } + } + if (flag) + r.string2Json(jf.getCastors().castToString(currentObj)); + } + + @Override + public Object fromJson(Object obj, Mirror mirror) throws Exception { + return Castors.me().castTo(obj, mirror.getType()); + } + + protected String doDateFormat(JsonFormat format, Date date, DateFormat df) { + if (df == null) + df = format.getDateFormat(); + if (df != null) { + if (format.getTimeZone() != null) + df.setTimeZone(format.getTimeZone()); + return df.format(date); + } + return null; + } +} diff --git a/src/org/nutz/json/handler/JsonEnumHandler.java b/src/org/nutz/json/handler/JsonEnumHandler.java new file mode 100644 index 0000000000..b0bc5458d9 --- /dev/null +++ b/src/org/nutz/json/handler/JsonEnumHandler.java @@ -0,0 +1,75 @@ +package org.nutz.json.handler; + +import java.io.IOException; +import java.util.Map; + +import org.nutz.json.JsonFormat; +import org.nutz.json.JsonRender; +import org.nutz.json.JsonShape; +import org.nutz.json.JsonTypeHandler; +import org.nutz.lang.Lang; +import org.nutz.lang.Mirror; +import org.nutz.lang.util.NutMap; + +/** + * + * @author wendal + * + */ +public class JsonEnumHandler extends JsonTypeHandler { + + public boolean supportFromJson(Mirror mirror, Object obj) { + return mirror.isEnum(); + } + + public boolean supportToJson(Mirror mirror, Object obj, JsonFormat jf) { + return mirror.isEnum(); + } + + @SuppressWarnings("rawtypes") + @Override + public void toJson(Mirror mirror, Object currentObj, JsonRender r, JsonFormat jf) throws IOException { + Mirror mr = mirror; + // 枚举 + if (mr.isEnum()) { + JsonShape shape = Mirror.getAnnotationDeep(mr.getType(), JsonShape.class); + if (shape == null || jf.isIgnoreJsonShape()) { + r.string2Json(((Enum) currentObj).name()); + } else { + NutMap map; + switch (shape.value()) { + case ORDINAL: + r.writeRaw(String.valueOf(((Enum) currentObj).ordinal())); + break; + case OBJECT: + map = Lang.obj2nutmap(currentObj); + if (map.isEmpty()) { + r.string2Json(((Enum) currentObj).name()); + } else { + r.map2Json(map); + } + break; + case OBJECTWITHNAME: + map = Lang.obj2nutmap(currentObj); + map.setv(shape.nameKey(), ((Enum) currentObj).name()); + r.map2Json(map); + break; + default: + r.string2Json(((Enum) currentObj).name()); + break; + } + } + } + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public Object fromJson(Object obj, Mirror mirror) throws Exception { + String name; + if (obj instanceof Map) { + name = (String) ((Map) obj).get("name"); + } else + name = String.valueOf(obj); + return Enum.valueOf((Class) mirror.getType(), name); + } +} diff --git a/src/org/nutz/json/handler/JsonIterableHandler.java b/src/org/nutz/json/handler/JsonIterableHandler.java new file mode 100644 index 0000000000..b7c57d8d7c --- /dev/null +++ b/src/org/nutz/json/handler/JsonIterableHandler.java @@ -0,0 +1,48 @@ +package org.nutz.json.handler; + +import java.io.IOException; +import java.io.Writer; +import java.util.Iterator; + +import org.nutz.json.JsonFormat; +import org.nutz.json.JsonRender; +import org.nutz.json.JsonTypeHandler; +import org.nutz.lang.Mirror; + +/** + * + * @author wendal + * + */ +public class JsonIterableHandler extends JsonTypeHandler { + + public boolean supportFromJson(Mirror mirror, Object obj) { + return false; + } + + public boolean supportToJson(Mirror mirror, Object obj, JsonFormat jf) { + return obj instanceof Iterable; + } + + @SuppressWarnings("rawtypes") + @Override + public void toJson(Mirror mirror, Object currentObj, JsonRender r, JsonFormat jf) throws IOException { + Writer writer = r.getWriter(); + Iterable iterable = (Iterable) currentObj; + writer.append('['); + for (Iterator it = iterable.iterator(); it.hasNext();) { + r.render(it.next()); + if (it.hasNext()) { + r.appendPairEnd(); + writer.append(' '); + } else + break; + } + writer.append(']'); + } + + @Override + public boolean shallCheckMemo() { + return true; + } +} diff --git a/src/org/nutz/json/handler/JsonJsonRenderHandler.java b/src/org/nutz/json/handler/JsonJsonRenderHandler.java new file mode 100644 index 0000000000..e6e7b55b0f --- /dev/null +++ b/src/org/nutz/json/handler/JsonJsonRenderHandler.java @@ -0,0 +1,22 @@ +package org.nutz.json.handler; + +import java.io.IOException; + +import org.nutz.json.JsonFormat; +import org.nutz.json.JsonRender; +import org.nutz.json.JsonTypeHandler; +import org.nutz.lang.Mirror; + +/** + * 支持 JsonRender + */ +public class JsonJsonRenderHandler extends JsonTypeHandler { + + public boolean supportToJson(Mirror mirror, Object obj, JsonFormat jf) { + return obj != null && obj instanceof JsonRender; + } + + public void toJson(Mirror mirror, Object currentObj, JsonRender r, JsonFormat jf) throws IOException { + ((JsonRender) currentObj).render(null); + } +} diff --git a/src/org/nutz/json/handler/JsonLocalDateLikeHandler.java b/src/org/nutz/json/handler/JsonLocalDateLikeHandler.java new file mode 100644 index 0000000000..a528d85b53 --- /dev/null +++ b/src/org/nutz/json/handler/JsonLocalDateLikeHandler.java @@ -0,0 +1,49 @@ +package org.nutz.json.handler; + +import java.io.IOException; +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; +import java.util.Locale; + +import org.nutz.castor.Castors; +import org.nutz.json.JsonFormat; +import org.nutz.json.JsonRender; +import org.nutz.json.JsonTypeHandler; +import org.nutz.lang.Mirror; + +public class JsonLocalDateLikeHandler extends JsonTypeHandler { + + @Override + public boolean supportFromJson(Mirror mirror, Object obj) { + return mirror.isLocalDateTimeLike(); + } + + @Override + public boolean supportToJson(Mirror mirror, Object obj, JsonFormat jf) { + return mirror.isLocalDateTimeLike(); + } + + @Override + public void toJson(Mirror mirror, Object currentObj, JsonRender r, JsonFormat jf) throws IOException { + String df = jf.getDateFormatRaw(); + if (mirror.getType().equals(LocalDate.class)){ + df = "yyyy-MM-dd"; + } + if (df == null) + df = "yyyy-MM-dd HH:mm:ss.SSS"; + Locale locale = null; + String tmp = jf.getLocale(); + if (tmp != null) + locale = Locale.forLanguageTag(tmp); + else + locale = Locale.getDefault(); + r.string2Json(DateTimeFormatter.ofPattern(df, locale).withZone(ZoneId.systemDefault()).format((TemporalAccessor) currentObj)); + } + + @Override + public Object fromJson(Object obj, Mirror mirror) throws Exception { + return Castors.me().castTo(obj, mirror.getType()); + } +} diff --git a/src/org/nutz/json/handler/JsonMapHandler.java b/src/org/nutz/json/handler/JsonMapHandler.java new file mode 100644 index 0000000000..6fb6788fad --- /dev/null +++ b/src/org/nutz/json/handler/JsonMapHandler.java @@ -0,0 +1,57 @@ +package org.nutz.json.handler; + +import java.io.IOException; +import java.util.Map; + +import org.nutz.json.JsonFormat; +import org.nutz.json.JsonRender; +import org.nutz.json.JsonTypeHandler; +import org.nutz.json.ToJson; +import org.nutz.lang.Invoking; +import org.nutz.lang.Mirror; + +/** + * + * @author wendal + * + */ +public class JsonMapHandler extends JsonTypeHandler { + + public boolean supportFromJson(Mirror mirror, Object obj) { + return mirror.isMap(); + } + + public boolean supportToJson(Mirror mirror, Object obj, JsonFormat jf) { + return mirror.isMap(); + } + + @SuppressWarnings("rawtypes") + public void toJson(Mirror mirror, Object currentObj, JsonRender r, JsonFormat jf) + throws IOException { + // 即使是 map 也支持 @ToJson 的声明 + ToJson tj = mirror.getAnnotation(ToJson.class); + if (null != tj) { + String methodName = tj.value(); + Invoking invk = mirror.getInvoking(methodName, jf); + if (null == invk) { + invk = mirror.getInvoking(methodName); + } + if (null != invk) { + String json = invk.invoke(currentObj).toString(); + r.writeRaw(json); + return; + } + } + // 用默认的 + r.map2Json((Map) currentObj); + } + + public Object fromJson(Object obj, Mirror mirror) throws Exception { + return null; + } + + @Override + public boolean shallCheckMemo() { + return true; + } +} diff --git a/src/org/nutz/json/handler/JsonMirrorHandler.java b/src/org/nutz/json/handler/JsonMirrorHandler.java new file mode 100644 index 0000000000..644b69e6d0 --- /dev/null +++ b/src/org/nutz/json/handler/JsonMirrorHandler.java @@ -0,0 +1,34 @@ +package org.nutz.json.handler; + +import java.io.IOException; + +import org.nutz.json.JsonFormat; +import org.nutz.json.JsonRender; +import org.nutz.json.JsonTypeHandler; +import org.nutz.lang.Lang; +import org.nutz.lang.Mirror; + +/** + * + * @author wendal + * + */ +public class JsonMirrorHandler extends JsonTypeHandler { + + public boolean supportFromJson(Mirror mirror, Object obj) { + return mirror.getType() == Mirror.class; + } + + public boolean supportToJson(Mirror mirror, Object obj, JsonFormat jf) { + return obj != null && obj instanceof Mirror; + } + + @SuppressWarnings("rawtypes") + public void toJson(Mirror mirror, Object currentObj, JsonRender r, JsonFormat jf) throws IOException { + r.string2Json(((Mirror) currentObj).getType().getName()); + } + + public Object fromJson(Object obj, Mirror mirror) throws Exception { + return Mirror.me(Lang.loadClass(String.valueOf(obj))); + } +} diff --git a/src/org/nutz/json/handler/JsonNumberHandler.java b/src/org/nutz/json/handler/JsonNumberHandler.java new file mode 100644 index 0000000000..ea6b1f1dd1 --- /dev/null +++ b/src/org/nutz/json/handler/JsonNumberHandler.java @@ -0,0 +1,51 @@ +package org.nutz.json.handler; + +import java.io.IOException; + +import org.nutz.castor.Castors; +import org.nutz.json.JsonFormat; +import org.nutz.json.JsonRender; +import org.nutz.json.JsonTypeHandler; +import org.nutz.lang.Mirror; + +/** + * + * @author wendal + * + */ +public class JsonNumberHandler extends JsonTypeHandler { + + @Override + public boolean supportFromJson(Mirror mirror, Object obj) { + return mirror.isNumber(); + } + + @Override + public boolean supportToJson(Mirror mirror, Object obj, JsonFormat jf) { + return Mirror.me(obj).isNumber(); + } + + @Override + public void toJson(Mirror mirror, Object currentObj, JsonRender r, JsonFormat jf) throws IOException { + String tmp = currentObj.toString(); + if ("NaN".equals(tmp)) { + // TODO 怎样才能应用上JsonFormat中是否忽略控制呢?因为此时已经写入了key: + r.writeRaw("null"); + } else { + // if (jf.getNumberFormat() != null) { + // tmp = jf.getNumberFormat().format(currentObj); + // } + if (currentObj instanceof Long && jf.isLongAsString()) { + r.string2Json(tmp); + } + else { + r.writeRaw(tmp); + } + } + } + + @Override + public Object fromJson(Object obj, Mirror mirror) throws Exception { + return Castors.me().castTo(obj, mirror.getType()); + } +} diff --git a/src/org/nutz/json/handler/JsonPojoHandler.java b/src/org/nutz/json/handler/JsonPojoHandler.java new file mode 100644 index 0000000000..3714945179 --- /dev/null +++ b/src/org/nutz/json/handler/JsonPojoHandler.java @@ -0,0 +1,140 @@ +package org.nutz.json.handler; + +import java.io.IOException; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import org.nutz.json.Json; +import org.nutz.json.JsonFormat; +import org.nutz.json.JsonRender; +import org.nutz.json.JsonTypeHandler; +import org.nutz.json.entity.JsonCallback; +import org.nutz.json.entity.JsonEntity; +import org.nutz.json.entity.JsonEntityField; +import org.nutz.json.impl.JsonPair; +import org.nutz.lang.FailToGetValueException; +import org.nutz.lang.Mirror; + +/** + * + * @author wendal + * + */ +public class JsonPojoHandler extends JsonTypeHandler { + + public boolean supportFromJson(Mirror mirror, Object obj) { + return false; + } + + public boolean supportToJson(Mirror mirror, Object obj, JsonFormat jf) { + return true; + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + public void toJson(Mirror _mirror, Object obj, JsonRender r, JsonFormat format) throws IOException { + if (null == obj) + return; + /* + * Default + */ + Class type = obj.getClass(); + if (type == JsonPojoHandler.class) { + return; + } + JsonEntity jen = Json.getEntity(Mirror.me(type)); + JsonCallback jsonCallback = jen.getJsonCallback(); + if (jsonCallback != null) { + if (jsonCallback.toJson(obj, format, r.getWriter())) + return; + } + List fields = jen.getFields(); + r.appendBraceBegin(); + r.increaseFormatIndent(); + ArrayList list = new ArrayList(fields.size()); + for (JsonEntityField jef : fields) { + if (jef.isIgnore()) + continue; + String name = jef.getName(); + try { + Object value = jef.getValue(obj); + // 判断是否应该被忽略 + if (r.isIgnore(name, value)) + continue; + Mirror mirror = jef.getMirror(); + // 以前曾经输出过 ... + if (null != value) { + // zozoh: 循环引用的默认行为,应该为 null,以便和其他语言交换数据 + if (mirror.isPojo()) { + if (r.memoContains(value)) + value = null; + } + } + if (null == value) { + // 处理各种类型的空值 + if (mirror != null) { + if (mirror.isStringLike()) { + if (format.isNullStringAsEmpty()) + value = ""; + } else if (mirror.isNumber()) { + if (format.isNullNumberAsZero()) + value = 0; + } else if (mirror.isCollection()) { + if (format.isNullListAsEmpty()) + value = Collections.EMPTY_LIST; + } else if (jef.getGenericType() == Boolean.class) { + if (format.isNullBooleanAsFalse()) + value = false; + } + } + } else { + // 如果是强制输出为字符串的 + if (jef.isForceString()) { + // 数组 + if (value.getClass().isArray()) { + String[] ss = new String[Array.getLength(value)]; + for (int i = 0; i < ss.length; i++) { + ss[i] = Array.get(value, i).toString(); + } + value = ss; + } + // 集合 + else if (value instanceof Collection) { + Collection col = (Collection) Mirror.me(value).born(); + for (Object ele : (Collection) value) { + col.add(ele.toString()); + } + value = col; + } + // 其他统统变字符串 + else { + value = r.value2string(jef, value); + } + } else if (jef.hasDataFormat() && value instanceof Date) { + value = jef.getDataFormat().format(value); + } else if (jef.hasDataFormat() && (mirror != null && mirror.isNumber())) { + value = jef.getDataFormat().format(value); + } + } + + // 加入输出列表 ... + list.add(new JsonPair(name, value)); + } + catch (FailToGetValueException e) {} + } + r.writeItem(list); + } + + public Object fromJson(Object obj, Mirror mirror) throws Exception { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean shallCheckMemo() { + return true; + } +} diff --git a/src/org/nutz/json/handler/JsonStringLikeHandler.java b/src/org/nutz/json/handler/JsonStringLikeHandler.java new file mode 100644 index 0000000000..913b9fb940 --- /dev/null +++ b/src/org/nutz/json/handler/JsonStringLikeHandler.java @@ -0,0 +1,34 @@ +package org.nutz.json.handler; + +import java.io.IOException; + +import org.nutz.castor.Castors; +import org.nutz.json.JsonFormat; +import org.nutz.json.JsonRender; +import org.nutz.json.JsonTypeHandler; +import org.nutz.lang.Mirror; + +/** + * + * @author wendal + * + */ +public class JsonStringLikeHandler extends JsonTypeHandler { + + public boolean supportFromJson(Mirror mirror, Object obj) { + return mirror.isStringLike() || mirror.isChar(); + } + + public boolean supportToJson(Mirror mirror, Object obj, JsonFormat jf) { + return mirror.isStringLike() || mirror.isChar(); + } + + public void toJson(Mirror mirror, Object currentObj, JsonRender r, JsonFormat jf) throws IOException { + r.string2Json(String.valueOf(currentObj)); + } + + @Override + public Object fromJson(Object obj, Mirror mirror) throws Exception { + return Castors.me().castTo(obj, mirror.getType()); + } +} diff --git a/src/org/nutz/json/impl/JsonCompileImplV2.java b/src/org/nutz/json/impl/JsonCompileImplV2.java index 76ed732ae9..e28d03b403 100644 --- a/src/org/nutz/json/impl/JsonCompileImplV2.java +++ b/src/org/nutz/json/impl/JsonCompileImplV2.java @@ -3,15 +3,17 @@ import java.io.IOException; import java.io.Reader; import java.math.BigDecimal; +import java.math.BigInteger; import java.util.ArrayList; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import org.nutz.conf.NutConf; import org.nutz.json.JsonException; import org.nutz.json.JsonParser; import org.nutz.lang.Lang; import org.nutz.lang.Nums; +import org.nutz.lang.util.NutMap; import org.nutz.mapl.MaplCompile; /** @@ -22,6 +24,7 @@ */ public class JsonCompileImplV2 implements JsonParser, MaplCompile { + @Override public Object parse(Reader reader) { return new JsonTokenScan(reader).read(); } @@ -69,11 +72,12 @@ protected void _nextToken() { while (true) { c = nextChar(); switch (c) { - case ' ': - case '\t': - case '\n': - case '\r': - continue; + case ' ': + case '\t': + case '\n': + case '\r': + continue; + default: } break; } @@ -92,25 +96,26 @@ protected void _nextToken() { OUT: while (true) { c = nextChar(); switch (c) { - case MapStart: - case MapEnd: - case ListStart: - case ListEnd: - case MapPair: - case Comma: - nextToken = nextToken2; - nextToken.type = c; - // log.debug("Break OtherString token : " + (char) c); - // log.debug("OtherString token : " + (char) token.type); - break OUT; - case ' ': - case '\t': - case '\r': - case '\n': - break OUT; - case '/': - skipComment(); - break OUT; + case MapStart: + case MapEnd: + case ListStart: + case ListEnd: + case MapPair: + case Comma: + nextToken = nextToken2; + nextToken.type = c; + // log.debug("Break OtherString token : " + (char) c); + // log.debug("OtherString token : " + (char) token.type); + break OUT; + case ' ': + case '\t': + case '\r': + case '\n': + break OUT; + case '/': + skipComment(); + break OUT; + default: } sb.append(c); } @@ -146,8 +151,9 @@ protected void skipComment() { while ((c = nextChar()) != '/') { c2 = c; } - if (c2 == '*') + if (c2 == '*') { return; + } } default: throw unexpectChar(c); @@ -159,9 +165,14 @@ protected String readString(char endEnd) { char c = 0; while ((c = nextChar()) != endEnd) { switch (c) { - case '\\': - c = parseSp(); - break; + case '\\': + char c2 = parseSp(); + if (c == c2 && NutConf.JSON_APPEND_ILLEGAL_ESCAPE) { + sb.append('\\'); + } + c = c2; + break; + default: } sb.append(c); } @@ -169,7 +180,7 @@ protected String readString(char endEnd) { } protected Map readMap() { - Map map = new LinkedHashMap(); + Map map = new NutMap(); boolean hasComma = false; OUT: while (true) { nextToken(); @@ -186,13 +197,15 @@ protected Map readMap() { } Object obj = readObject(MapEnd); if (obj == COMMA) { - if (hasComma) + if (hasComma) { throw unexpectChar((char) Comma); + } hasComma = true; continue; } - if (obj == END) + if (obj == END) { throw unexpectChar((char) token.type); + } map.put(key, obj); hasComma = false; break; @@ -210,11 +223,13 @@ protected List readList() { boolean hasComma = false; while (true) { Object obj = readObject(ListEnd); - if (obj == END) + if (obj == END) { break; + } if (obj == COMMA) { - if (hasComma) + if (hasComma) { throw unexpectChar((char) Comma); + } hasComma = true; continue; } @@ -227,83 +242,99 @@ protected List readList() { protected Object readObject(int endTag) { nextToken(); switch (token.type) { - case MapStart: - return readMap(); - case ListStart: - return readList(); - case SimpleString: - return token.value; - case OtherString: - String value = token.value; - int len = value.length(); - if (len == 0) - return ""; - switch (value.charAt(0)) { - case 't': - if ("true".equals(value)) - return true; - break; - case 'f': - if ("false".equals(value)) - return false; - break; - case 'n': - if ("null".endsWith(value)) - return null; - break; - case 'u': - if ("undefined".endsWith(value)) - return null; - break; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case '.': - case '-': - // 看来是数字哦 - if (token.value.length() > 0) { - switch (token.value.charAt(token.value.length() - 1)) { - case 'l': - case 'L': - return Long.parseLong(token.value.substring(0, token.value.length() - 1)); + case MapStart: + return readMap(); + case ListStart: + return readList(); + case SimpleString: + return token.value; + case OtherString: + String value = token.value; + int len = value.length(); + if (len == 0) { + return ""; + } + switch (value.charAt(0)) { + case 't': + if ("true".equals(value)) { + return true; + } + break; case 'f': - case 'F': - return Float.parseFloat(token.value.substring(0, token.value.length() - 1)); - default: - if (token.value.contains("e") || token.value.contains("E")) { - return new BigDecimal(token.value); + if ("false".equals(value)) { + return false; } - if (token.value.contains(".")) { - return Double.parseDouble(token.value); + break; + case 'n': + if ("null".endsWith(value)) { + return null; } - } + break; + case 'u': + if ("undefined".endsWith(value)) { + return null; + } + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '.': + case '-': + // 看来是数字哦 + if (token.value.length() > 0) { + switch (token.value.charAt(token.value.length() - 1)) { + case 'l': + case 'L': + return toLong(token.value.substring(0, token.value.length() - 1)); + case 'f': + case 'F': + return Float.parseFloat(token.value.substring(0, token.value.length() - 1)); + default: + if (token.value.contains("e") || token.value.contains("E")) { + return new BigDecimal(token.value); + } + if (token.value.contains(".")) { + return Double.parseDouble(token.value); + } + } + } + Number n = toLong(token.value); + if (n instanceof Long && Integer.MAX_VALUE >= n.longValue() && n.longValue() >= Integer.MIN_VALUE) { + return n.intValue(); + } + return n; + default: } - Nums.Radix r = Nums.evalRadix(token.value); - long n = 0; - try { - n = Long.parseLong(r.val, r.radix); - } catch (Throwable e) { - n = Long.parseLong(token.value); + throw new JsonException(row, col, value.charAt(0), "Unexpect String = " + value); + default: + if (token.type == endTag) { + return END; } - if (Integer.MAX_VALUE >= n && n >= Integer.MIN_VALUE) { - return (int) n; + if (token.type == Comma) { + return COMMA; } - return n; + throw unexpectChar((char) token.type); + } + } + + protected Number toLong(String value) { + Nums.Radix r = Nums.evalRadix(value); + try { + return Long.parseLong(r.val, r.radix); + } catch (Throwable e) { + try { + return Long.parseLong(value); + } + catch (NumberFormatException e1) { + return new BigInteger(value); } - throw new JsonException(row, col, value.charAt(0), "Unexpect String = " + value); - default: - if (token.type == endTag) - return END; - if (token.type == Comma) - return COMMA; - throw unexpectChar((char) token.type); } } @@ -334,10 +365,12 @@ public Object read() { case '(': while (true) { int z = nextChar(); - if (z == '{') + if (z == '{') { return readMap(); - if (z == '[') + } + if (z == '[') { return readList(); + } } case MapStart: return readMap(); @@ -349,18 +382,20 @@ public Object read() { default: nextToken = nextToken2; nextToken.type = OtherString; - if (add) + if (add) { nextToken.value = (char) c + Lang.readAll(reader); - else + } else { nextToken.value = Lang.readAll(reader); + } return readObject(-1); } } char nextChar() { int c = readChar(); - if (c == -1) + if (c == -1) { throw new JsonException("Unexpect EOF"); + } return (char) c; } @@ -383,8 +418,9 @@ protected char parseSp() { return '/'; case 'u': char[] hex = new char[4]; - for (int i = 0; i < 4; i++) + for (int i = 0; i < 4; i++) { hex[i] = nextChar(); + } return (char) Integer.valueOf(new String(hex), 16).intValue(); case 'b': // 这个支持一下又何妨? return ' ';// 空格 @@ -393,6 +429,10 @@ protected char parseSp() { case 'f': return '\f'; default: + // 容忍非法转义 + if (NutConf.JSON_ALLOW_ILLEGAL_ESCAPE) { + return c; + } throw unexpectChar(c); } } @@ -439,6 +479,7 @@ class JsonToken { int type; String value; + @Override public String toString() { return "[" + (char) type + " " + value + "]" + hashCode(); } diff --git a/src/org/nutz/json/impl/JsonPair.java b/src/org/nutz/json/impl/JsonPair.java new file mode 100644 index 0000000000..487caab952 --- /dev/null +++ b/src/org/nutz/json/impl/JsonPair.java @@ -0,0 +1,12 @@ +package org.nutz.json.impl; + +public class JsonPair { + + public JsonPair(String name, Object value) { + this.name = name; + this.value = value; + } + + String name; + Object value; +} \ No newline at end of file diff --git a/src/org/nutz/json/impl/JsonRenderImpl.java b/src/org/nutz/json/impl/JsonRenderImpl.java index 53cf313a76..339ec729df 100644 --- a/src/org/nutz/json/impl/JsonRenderImpl.java +++ b/src/org/nutz/json/impl/JsonRenderImpl.java @@ -2,12 +2,9 @@ import java.io.IOException; import java.io.Writer; -import java.lang.reflect.Array; import java.text.DateFormat; import java.text.Format; import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.Iterator; @@ -20,15 +17,10 @@ import org.nutz.json.Json; import org.nutz.json.JsonFormat; import org.nutz.json.JsonRender; -import org.nutz.json.JsonShape; -import org.nutz.json.entity.JsonEntity; +import org.nutz.json.JsonTypeHandler; import org.nutz.json.entity.JsonEntityField; -import org.nutz.json.entity.JsonCallback; -import org.nutz.lang.FailToGetValueException; -import org.nutz.lang.Lang; import org.nutz.lang.Mirror; import org.nutz.lang.Strings; -import org.nutz.lang.util.NutMap; /** * @author zozoh(zozohtnt@gmail.com) @@ -47,7 +39,7 @@ public class JsonRenderImpl implements JsonRender { private Set memo = new HashSet(); private boolean compact; - + /** * 缩进 */ @@ -76,93 +68,27 @@ public void setWriter(Writer writer) { public void render(Object obj) throws IOException { if (null == obj) { appendNull(); - } else if (obj instanceof JsonRender) { - ((JsonRender) obj).render(null); - } else if (obj instanceof Class) { - string2Json(((Class) obj).getName()); - } else if (obj instanceof Mirror) { - string2Json(((Mirror) obj).getType().getName()); - } else { - Mirror mr = Mirror.me(obj.getClass()); - // 枚举 - if (mr.isEnum()) { - JsonShape shape = Mirror.getAnnotationDeep(mr.getType(), JsonShape.class); - if (shape == null) { - string2Json(((Enum) obj).name()); - } else { - switch (shape.value()) { - case ORDINAL: - writer.append(String.valueOf(((Enum) obj).ordinal())); - break; - case OBJECT: - NutMap map = Lang.obj2nutmap(obj); - if (map.isEmpty()) { - string2Json(((Enum) obj).name()); - } else { - map2Json(map); - } - break; - default: - string2Json(((Enum) obj).name()); - break; - } - } - } - // 数字,布尔等 - else if (mr.isNumber()) { - String tmp = obj.toString(); - if (tmp.equals("NaN")) { - // TODO 怎样才能应用上JsonFormat中是否忽略控制呢? - // 因为此时已经写入了key: - writer.write("null"); - } else - writer.write(tmp); - } else if (mr.isBoolean()) { - writer.append(obj.toString()); - } - // 字符串 - else if (mr.isStringLike() || mr.isChar()) { - string2Json(obj.toString()); - } - // 日期时间 - else if (mr.isDateTimeLike()) { - boolean flag = true; - if (obj instanceof Date) { - String _val = doDateFormat((Date) obj, null); - if (_val != null) { - string2Json(_val); - flag = false; + return; + } + Mirror mirror = Mirror.me(obj); + for (JsonTypeHandler handler : Json.getTypeHandlers()) { + if (handler.supportToJson(mirror, obj, format)) { + if (handler.shallCheckMemo()) { + if (memo.contains(obj)) { + writer.write("null"); + return; } + memo.add(obj); + handler.toJson(mirror, obj, this, format); + memo.remove(obj); } - if (flag) - string2Json(format.getCastors().castToString(obj)); - } - // 其他 - else { - if (memo.contains(obj)) { - writer.write("null"); - return; - } - memo.add(obj); - // Map - if (obj instanceof Map) { - map2Json((Map) obj); - } - // 集合 - else if (obj instanceof Iterable) { - coll2Json((Iterable) obj); - } - // 数组 - else if (obj.getClass().isArray()) { - array2Json(obj); - } - // 普通 Java 对象 - else { - pojo2Json(obj); - } - memo.remove(obj); + else + handler.toJson(mirror, obj, this, format); + return; } } + // 理论上不会到这来,防御用 + this.string2Json(String.valueOf(obj)); } public JsonRenderImpl() {} @@ -174,25 +100,29 @@ public JsonRenderImpl(Writer writer, JsonFormat format) { private static final Pattern p = Pattern.compile("^[a-z_A-Z$]+[a-zA-Z_0-9$]*$"); - private void appendName(String name) throws IOException { + @Override + public void appendName(String name) throws IOException { if (format.isQuoteName() || !p.matcher(name).find()) string2Json(name); else writer.append(name); } - private void appendPairBegin() throws IOException { + @Override + public void appendPairBegin() throws IOException { if (!compact) { writer.append(NL); doIntent(); } } - private void appendPairSep() throws IOException { + @Override + public void appendPairSep() throws IOException { writer.append(!compact ? ": " : ":"); } - protected void appendPair(boolean needPairEnd, String name, Object value) throws IOException { + @Override + public void appendPair(boolean needPairEnd, String name, Object value) throws IOException { appendPairBegin(); appendName(name); appendPairSep(); @@ -202,21 +132,25 @@ protected void appendPair(boolean needPairEnd, String name, Object value) throws } } - private boolean isIgnore(String name, Object value) { + @Override + public boolean isIgnore(String name, Object value) { if (null == value && format.isIgnoreNull()) return true; return format.ignore(name); } - private void appendPairEnd() throws IOException { + @Override + public void appendPairEnd() throws IOException { writer.append(','); } - private void appendBraceBegin() throws IOException { + @Override + public void appendBraceBegin() throws IOException { writer.append('{'); } - private void appendBraceEnd() throws IOException { + @Override + public void appendBraceEnd() throws IOException { if (!compact) { writer.append(NL); doIntent(); @@ -224,146 +158,47 @@ private void appendBraceEnd() throws IOException { writer.append('}'); } - static class Pair { - - public Pair(String name, Object value) { - this.name = name; - this.value = value; - } - - String name; - Object value; - } - @SuppressWarnings({"unchecked"}) - private void map2Json(Map map) throws IOException { + public void map2Json(Map map) throws IOException { if (null == map) return; appendBraceBegin(); increaseFormatIndent(); - ArrayList list = new ArrayList(map.size()); + ArrayList list = new ArrayList(map.size()); Set> entrySet = map.entrySet(); for (Entry entry : entrySet) { String name = null == entry.getKey() ? "null" : entry.getKey().toString(); Object value = entry.getValue(); if (!this.isIgnore(name, value)) - list.add(new Pair(name, value)); + list.add(new JsonPair(name, value)); } writeItem(list); } - @SuppressWarnings("unchecked") - private void pojo2Json(Object obj) throws IOException { - if (null == obj) - return; - /* - * Default - */ - Class type = obj.getClass(); - JsonEntity jen = Json.getEntity(Mirror.me(type)); - JsonCallback jsonCallback = jen.getJsonCallback(); - if (jsonCallback != null) { - if (jsonCallback.toJson(obj, format, writer)) - return; - } - List fields = jen.getFields(); - appendBraceBegin(); - increaseFormatIndent(); - ArrayList list = new ArrayList(fields.size()); - for (JsonEntityField jef : fields) { - if (jef.isIgnore()) - continue; - String name = jef.getName(); - try { - Object value = jef.getValue(obj); - // 判断是否应该被忽略 - if (this.isIgnore(name, value)) - continue; - Mirror mirror = jef.getMirror(); - // 以前曾经输出过 ... - if (null != value) { - // zozoh: 循环引用的默认行为,应该为 null,以便和其他语言交换数据 - if (mirror.isPojo()) { - if (memo.contains(value)) - value = null; - } - } - if (null == value) { - // 处理各种类型的空值 - if (mirror != null) { - if (mirror.isStringLike()) { - if (format.isNullStringAsEmpty()) - value = ""; - } else if (mirror.isNumber()) { - if (format.isNullNumberAsZero()) - value = 0; - } else if (mirror.isCollection()) { - if (format.isNullListAsEmpty()) - value = Collections.EMPTY_LIST; - } else if (jef.getGenericType() == Boolean.class) { - if (format.isNullBooleanAsFalse()) - value = false; - } - } - } else { - // 如果是强制输出为字符串的 - if (jef.isForceString()) { - // 数组 - if (value.getClass().isArray()) { - String[] ss = new String[Array.getLength(value)]; - for (int i = 0; i < ss.length; i++) { - ss[i] = Array.get(value, i).toString(); - } - value = ss; - } - // 集合 - else if (value instanceof Collection) { - Collection col = (Collection) Mirror.me(value).born(); - for (Object ele : (Collection) value) { - col.add(ele.toString()); - } - value = col; - } - // 其他统统变字符串 - else { - value = value2string(jef, value); - } - } else if (jef.hasDataFormat() && value instanceof Date) { - value = jef.getDataFormat().format(value); - } else if (jef.hasDataFormat() && (mirror != null && mirror.isNumber())) { - value = jef.getDataFormat().format(value); - } - } - - // 加入输出列表 ... - list.add(new Pair(name, value)); - } - catch (FailToGetValueException e) {} - } - writeItem(list); - } - - private void writeItem(List list) throws IOException { - Iterator it = list.iterator(); + @Override + public void writeItem(List list) throws IOException { + Iterator it = list.iterator(); while (it.hasNext()) { - Pair p = it.next(); + JsonPair p = it.next(); appendPair(it.hasNext(), p.name, p.value); } decreaseFormatIndent(); appendBraceEnd(); } - private void decreaseFormatIndent() { + @Override + public void decreaseFormatIndent() { if (!compact) indent--; } - private void increaseFormatIndent() { + @Override + public void increaseFormatIndent() { if (!compact) indent++; } - private void string2Json(String s) throws IOException { + public void string2Json(String s) throws IOException { if (null == s) appendNull(); else { @@ -402,9 +237,7 @@ private void string2Json(String s) throws IOException { else writer.write(u.toUpperCase()); } else { - if (c < ' ' - || (c >= '\u0080' && c < '\u00a0') - || (c >= '\u2000' && c < '\u2100')) { + if (c < ' ' || (c >= '\u0080' && c < '\u00a0') || (c >= '\u2000' && c < '\u2100')) { writer.write("\\u"); String hhhh = Integer.toHexString(c); writer.write("0000", 0, 4 - hhhh.length()); @@ -419,36 +252,8 @@ private void string2Json(String s) throws IOException { } } - private void array2Json(Object obj) throws IOException { - writer.append('['); - int len = Array.getLength(obj) - 1; - if (len > -1) { - int i; - for (i = 0; i < len; i++) { - render(Array.get(obj, i)); - appendPairEnd(); - writer.append(' '); - } - render(Array.get(obj, i)); - } - writer.append(']'); - } - - private void coll2Json(Iterable iterable) throws IOException { - writer.append('['); - for (Iterator it = iterable.iterator(); it.hasNext();) { - render(it.next()); - if (it.hasNext()) { - appendPairEnd(); - writer.append(' '); - } else - break; - } - writer.append(']'); - } - - protected String value2string(JsonEntityField jef, Object value) { - + @Override + public String value2string(JsonEntityField jef, Object value) { Format df = jef.getDataFormat(); if (df == null) { Mirror mirror = Mirror.me(value); @@ -488,4 +293,15 @@ protected String doDateFormat(Date date, DateFormat df) { } return null; } + + @Override + public void writeRaw(String raw) throws IOException { + writer.write(raw); + } + + @Override + public boolean memoContains(Object obj) { + return memo.contains(obj); + } + } diff --git a/src/org/nutz/lang/Code.java b/src/org/nutz/lang/Code.java index 9fa48d1d4c..aa7b7dabb9 100644 --- a/src/org/nutz/lang/Code.java +++ b/src/org/nutz/lang/Code.java @@ -6,6 +6,8 @@ import java.io.FileReader; import java.io.IOException; +import org.nutz.lang.util.Regex; + /** * 一个统计代码的工具 * @@ -190,7 +192,7 @@ public static CodeAnalysisResult countingCode(File file, if (line.endsWith(conf.multiLineCommentEnd)) { comment = false; } - } else if (line.matches(conf.emptyLinePattern)) { + } else if (Regex.match(conf.emptyLinePattern, line)) { // 空白行(多行注解内的空白行不算在内) whiteLines++; } else if (line.startsWith(conf.singleLineCommentStart) diff --git a/src/org/nutz/lang/Configurable.java b/src/org/nutz/lang/Configurable.java new file mode 100644 index 0000000000..df473199ac --- /dev/null +++ b/src/org/nutz/lang/Configurable.java @@ -0,0 +1,8 @@ +package org.nutz.lang; + +import org.nutz.lang.util.NutMap; + +public interface Configurable { + + void setupProperties(NutMap conf); +} diff --git a/src/org/nutz/lang/Dumps.java b/src/org/nutz/lang/Dumps.java index bd511f9f21..a3e80651d2 100644 --- a/src/org/nutz/lang/Dumps.java +++ b/src/org/nutz/lang/Dumps.java @@ -50,7 +50,7 @@ public static String matcherFound(Matcher m) { m.regionStart(), m.regionEnd())); for (int i = 0; i <= m.groupCount(); i++) - sb.append(String.format("%2d:[%3d,%3d) %s\n", i, m.start(i), m.end(i), m.group(i))); + sb.append(String.format("%2d:[%3d,%3d) `%s`\n", i, m.start(i), m.end(i), m.group(i))); return sb.toString(); } diff --git a/src/org/nutz/lang/Files.java b/src/org/nutz/lang/Files.java index 33d8cb6e31..8accb245f6 100644 --- a/src/org/nutz/lang/Files.java +++ b/src/org/nutz/lang/Files.java @@ -22,6 +22,7 @@ import org.nutz.lang.util.Callback; import org.nutz.lang.util.ClassTools; import org.nutz.lang.util.Disks; +import org.nutz.lang.util.Regex; import org.nutz.log.Logs; /** @@ -298,6 +299,7 @@ public static String getSuffixName(File f) { public static String getSuffixName(String path) { if (null == path) return null; + path = path.replace('\\', '/'); int p0 = path.lastIndexOf('.'); int p1 = path.lastIndexOf('/'); if (-1 == p0 || p0 < p1) @@ -324,6 +326,7 @@ public static String getSuffix(File f) { public static String getSuffix(String path) { if (null == path) return null; + path = path.replace('\\', '/'); int p0 = path.lastIndexOf('.'); int p1 = path.lastIndexOf('/'); if (-1 == p0 || p0 < p1) @@ -345,7 +348,7 @@ public static ZipEntry[] findEntryInZip(ZipFile zip, String regex) { Enumeration en = zip.entries(); while (en.hasMoreElements()) { ZipEntry ze = en.nextElement(); - if (null == regex || ze.getName().matches(regex)) + if (null == regex || Regex.match(regex, ze.getName())) list.add(ze); } return list.toArray(new ZipEntry[list.size()]); @@ -424,7 +427,8 @@ public static File createDirIfNoExists(String path) { } } if (!f.isDirectory()) - throw Lang.makeThrow("'%s' should be a directory or don't have permission to create it!", path); + throw Lang.makeThrow("'%s' should be a directory or don't have permission to create it!", + path); return f; } @@ -501,12 +505,14 @@ public enum LsMode { /** * 仅文件 */ - FILE, /** - * 仅目录 - */ - DIR, /** - * 文件和目录 - */ + FILE, + /** + * 仅目录 + */ + DIR, + /** + * 文件和目录 + */ ALL } @@ -1068,6 +1074,30 @@ public static String getName(String path) { return path; } + /** + * 获取一个文件对象绝对路径,并且是跨平台统一的格式。即,分隔符均为/ + * + * @param f + * 文件对象 + * @return 格式化后的路径,所有分隔符会统一替换为 / + * @see #formedPath(String) + */ + public static String getAbsPath(File f) { + return formedPath(f.getAbsolutePath()); + } + + /** + * @param path + * 路径 + * @return 格式化后的路径,所有分隔符会统一替换为 / + */ + public static String formedPath(String path) { + if (null == path) { + return null; + } + return path.replace('\\', '/'); + } + /** * 将一个目录下的特殊名称的目录彻底删除,比如 '.svn' 或者 '.cvs' * @@ -1080,7 +1110,7 @@ public static String getName(String path) { public static void cleanAllFolderInSubFolderes(File dir, String name) throws IOException { File[] files = dir.listFiles(); if (files == null) - return; + return; for (File d : files) { if (d.isDirectory()) if (d.getName().equalsIgnoreCase(name)) @@ -1279,7 +1309,7 @@ public static void readLine(File f, Callback callback) { Streams.safeClose(br); } } - + public static int readRange(File f, int pos, byte[] buf, int at, int len) { try { if (f == null || !f.exists()) @@ -1289,7 +1319,7 @@ public static int readRange(File f, int pos, byte[] buf, int at, int len) { return 0; len = Math.min(len, buf.length - at); if (pos + len > fsize) { - len = (int)(fsize - pos); + len = (int) (fsize - pos); } RandomAccessFile raf = new RandomAccessFile(f, "r"); raf.seek(pos); @@ -1301,7 +1331,7 @@ public static int readRange(File f, int pos, byte[] buf, int at, int len) { return -1; } } - + public static int writeRange(File f, int pos, byte[] buf, int at, int len) { try { if (f == null || !f.exists()) diff --git a/src/org/nutz/lang/Lang.java b/src/org/nutz/lang/Lang.java index 2951b7c5d4..a869985b96 100644 --- a/src/org/nutz/lang/Lang.java +++ b/src/org/nutz/lang/Lang.java @@ -1,2815 +1,3000 @@ -package org.nutz.lang; - -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.PrintStream; -import java.io.Reader; -import java.io.StringReader; -import java.io.Writer; -import java.lang.reflect.Array; -import java.lang.reflect.Field; -import java.lang.reflect.GenericArrayType; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; -import java.lang.reflect.WildcardType; -import java.nio.charset.Charset; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Properties; -import java.util.Queue; -import java.util.Set; -import java.util.regex.Pattern; - -import javax.servlet.http.HttpServletRequest; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.ParserConfigurationException; - -import org.nutz.castor.Castors; -import org.nutz.castor.FailToCastObjectException; -import org.nutz.dao.entity.annotation.Column; -import org.nutz.json.Json; -import org.nutz.lang.reflect.ReflectTool; -import org.nutz.lang.stream.StringInputStream; -import org.nutz.lang.stream.StringOutputStream; -import org.nutz.lang.stream.StringWriter; -import org.nutz.lang.util.Context; -import org.nutz.lang.util.NutMap; -import org.nutz.lang.util.NutType; -import org.nutz.lang.util.SimpleContext; - -/** - * 这些帮助函数让 Java 的某些常用功能变得更简单 - * - * @author zozoh(zozohtnt@gmail.com) - * @author wendal(wendal1985@gmail.com) - * @author bonyfish(mc02cxj@gmail.com) - * @author wizzer(wizzer.cn@gmail.com) - */ -public abstract class Lang { - - public static int HASH_BUFF_SIZE = 16 * 1024; - - private static final Pattern IPV4_PATTERN = Pattern.compile("^(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}$"); - - private static final Pattern IPV6_STD_PATTERN = Pattern.compile("^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$"); - - private static final Pattern IPV6_HEX_COMPRESSED_PATTERN = Pattern.compile("^((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)$"); - - public static boolean isIPv4Address(final String input) { - return IPV4_PATTERN.matcher(input).matches(); - } - - public static boolean isIPv6StdAddress(final String input) { - return IPV6_STD_PATTERN.matcher(input).matches(); - } - - public static boolean isIPv6HexCompressedAddress(final String input) { - return IPV6_HEX_COMPRESSED_PATTERN.matcher(input).matches(); - } - - public static boolean isIPv6Address(final String input) { - return isIPv6StdAddress(input) || isIPv6HexCompressedAddress(input); - } - - public static ComboException comboThrow(Throwable... es) { - ComboException ce = new ComboException(); - for (Throwable e : es) - ce.add(e); - return ce; - } - - /** - * 生成一个未实现的运行时异常 - * - * @return 一个未实现的运行时异常 - */ - public static RuntimeException noImplement() { - return new RuntimeException("Not implement yet!"); - } - - /** - * 生成一个不可能的运行时异常 - * - * @return 一个不可能的运行时异常 - */ - public static RuntimeException impossible() { - return new RuntimeException("r u kidding me?! It is impossible!"); - } - - /** - * 根据格式化字符串,生成运行时异常 - * - * @param format - * 格式 - * @param args - * 参数 - * @return 运行时异常 - */ - public static RuntimeException makeThrow(String format, Object... args) { - return new RuntimeException(String.format(format, args)); - } - - /** - * 根据格式化字符串,生成一个指定的异常。 - * - * @param classOfT - * 异常类型, 需要有一个字符串为参数的构造函数 - * @param format - * 格式 - * @param args - * 参数 - * @return 异常对象 - */ - @SuppressWarnings("unchecked") - public static T makeThrow(Class classOfT, - String format, - Object... args) { - if (classOfT == RuntimeException.class) - return (T) new RuntimeException(String.format(format, args)); - return Mirror.me(classOfT).born(String.format(format, args)); - } - - /** - * 将抛出对象包裹成运行时异常,并增加自己的描述 - * - * @param e - * 抛出对象 - * @param fmt - * 格式 - * @param args - * 参数 - * @return 运行时异常 - */ - public static RuntimeException wrapThrow(Throwable e, String fmt, Object... args) { - return new RuntimeException(String.format(fmt, args), e); - } - - /** - * 用运行时异常包裹抛出对象,如果抛出对象本身就是运行时异常,则直接返回。 - *

- * 如果是 InvocationTargetException,那么将其剥离,只包裹其 TargetException - * - * @param e - * 抛出对象 - * @return 运行时异常 - */ - public static RuntimeException wrapThrow(Throwable e) { - if (e instanceof RuntimeException) - return (RuntimeException) e; - if (e instanceof InvocationTargetException) - return wrapThrow(((InvocationTargetException) e).getTargetException()); - return new RuntimeException(e); - } - - /** - * 用一个指定可抛出类型来包裹一个抛出对象。这个指定的可抛出类型需要有一个构造函数 接受 Throwable 类型的对象 - * - * @param e - * 抛出对象 - * @param wrapper - * 包裹类型 - * @return 包裹后对象 - */ - @SuppressWarnings("unchecked") - public static T wrapThrow(Throwable e, Class wrapper) { - if (wrapper.isAssignableFrom(e.getClass())) - return (T) e; - return Mirror.me(wrapper).born(e); - } - - public static Throwable unwrapThrow(Throwable e) { - if (e == null) - return null; - if (e instanceof InvocationTargetException) { - InvocationTargetException itE = (InvocationTargetException) e; - if (itE.getTargetException() != null) - return unwrapThrow(itE.getTargetException()); - } - if (e instanceof RuntimeException && e.getCause() != null) - return unwrapThrow(e.getCause()); - return e; - } - - public static boolean isCauseBy(Throwable e, Class causeType) { - if (e.getClass() == causeType) - return true; - Throwable cause = e.getCause(); - if (null == cause) - return false; - return isCauseBy(cause, causeType); - } - - /** - * 判断两个对象是否相等。 这个函数用处是: - *

    - *
  • 可以容忍 null - *
  • 可以容忍不同类型的 Number - *
  • 对数组,集合, Map 会深层比较 - *
- * 当然,如果你重写的 equals 方法会优先 - * - * @param a0 - * 比较对象1 - * @param a1 - * 比较对象2 - * @return 是否相等 - */ - public static boolean equals(Object a0, Object a1) { - if (a0 == a1) - return true; - - if (a0 == null && a1 == null) - return true; - - if (a0 == null || a1 == null) - return false; - - // 简单的判断是否等于 - if (a0.equals(a1)) - return true; - - Mirror mi = Mirror.me(a0); - - // 简单类型,变字符串比较,或者正则表达式 - if (mi.isSimple() || mi.is(Pattern.class)) { - return a0.toString().equals(a1.toString()); - } - - // 如果类型就不能互相转换,那么一定是错的 - if (!a0.getClass().isAssignableFrom(a1.getClass()) - && !a1.getClass().isAssignableFrom(a0.getClass())) - return false; - - // Map - if (a0 instanceof Map && a1 instanceof Map) { - Map m1 = (Map) a0; - Map m2 = (Map) a1; - if (m1.size() != m2.size()) - return false; - for (Entry e : m1.entrySet()) { - Object key = e.getKey(); - if (!m2.containsKey(key) || !equals(m1.get(key), m2.get(key))) - return false; - } - return true; - } - // 数组 - else if (a0.getClass().isArray() && a1.getClass().isArray()) { - int len = Array.getLength(a0); - if (len != Array.getLength(a1)) - return false; - for (int i = 0; i < len; i++) { - if (!equals(Array.get(a0, i), Array.get(a1, i))) - return false; - } - return true; - } - // 集合 - else if (a0 instanceof Collection && a1 instanceof Collection) { - Collection c0 = (Collection) a0; - Collection c1 = (Collection) a1; - if (c0.size() != c1.size()) - return false; - - Iterator it0 = c0.iterator(); - Iterator it1 = c1.iterator(); - - while (it0.hasNext()) { - Object o0 = it0.next(); - Object o1 = it1.next(); - if (!equals(o0, o1)) - return false; - } - - return true; - } - - // 一定不相等 - return false; - } - - /** - * 判断一个数组内是否包括某一个对象。 它的比较将通过 equals(Object,Object) 方法 - * - * @param array - * 数组 - * @param ele - * 对象 - * @return true 包含 false 不包含 - */ - public static boolean contains(T[] array, T ele) { - if (null == array) - return false; - for (T e : array) { - if (equals(e, ele)) - return true; - } - return false; - } - - /** - * 从一个文本输入流读取所有内容,并将该流关闭 - * - * @param reader - * 文本输入流 - * @return 输入流所有内容 - */ - public static String readAll(Reader reader) { - if (!(reader instanceof BufferedReader)) - reader = new BufferedReader(reader); - try { - StringBuilder sb = new StringBuilder(); - - char[] data = new char[64]; - int len; - while (true) { - if ((len = reader.read(data)) == -1) - break; - sb.append(data, 0, len); - } - return sb.toString(); - } - catch (IOException e) { - throw Lang.wrapThrow(e); - } - finally { - Streams.safeClose(reader); - } - } - - /** - * 将一段字符串写入一个文本输出流,并将该流关闭 - * - * @param writer - * 文本输出流 - * @param str - * 字符串 - */ - public static void writeAll(Writer writer, String str) { - try { - writer.write(str); - writer.flush(); - } - catch (IOException e) { - throw Lang.wrapThrow(e); - } - finally { - Streams.safeClose(writer); - } - } - - /** - * 根据一段文本模拟出一个输入流对象 - * - * @param cs - * 文本 - * @return 输出流对象 - */ - public static InputStream ins(CharSequence cs) { - return new StringInputStream(cs); - } - - /** - * 根据一段文本模拟出一个文本输入流对象 - * - * @param cs - * 文本 - * @return 文本输出流对象 - */ - public static Reader inr(CharSequence cs) { - return new StringReader(cs.toString()); - } - - /** - * 根据一个 StringBuilder 模拟一个文本输出流对象 - * - * @param sb - * StringBuilder 对象 - * @return 文本输出流对象 - */ - public static Writer opw(StringBuilder sb) { - return new StringWriter(sb); - } - - /** - * 根据一个 StringBuilder 模拟一个输出流对象 - * - * @param sb - * StringBuilder 对象 - * @return 输出流对象 - */ - public static StringOutputStream ops(StringBuilder sb) { - return new StringOutputStream(sb); - } - - /** - * 较方便的创建一个数组,比如: - * - *
-     * String[] strs = Lang.array("A", "B", "A"); => ["A","B","A"]
-     * 
- * - * @param eles - * 可变参数 - * @return 数组对象 - */ - public static T[] array(T... eles) { - return eles; - } - - /** - * 较方便的创建一个没有重复的数组,比如: - * - *
-     * String[] strs = Lang.arrayUniq("A","B","A");  => ["A","B"]
-     * String[] strs = Lang.arrayUniq();  => null
-     * 
- * - * 返回的顺序会遵循输入的顺序 - * - * @param eles - * 可变参数 - * @return 数组对象 - */ - @SuppressWarnings("unchecked") - public static T[] arrayUniq(T... eles) { - if (null == eles || eles.length == 0) - return null; - // 记录重复 - HashSet set = new HashSet(eles.length); - for (T ele : eles) { - set.add(ele); - } - // 循环 - T[] arr = (T[]) Array.newInstance(eles[0].getClass(), set.size()); - int index = 0; - for (T ele : eles) { - if (set.remove(ele)) - Array.set(arr, index++, ele); - } - return arr; - - } - - /** - * 判断一个对象是否为空。它支持如下对象类型: - *
    - *
  • null : 一定为空 - *
  • 数组 - *
  • 集合 - *
  • Map - *
  • 其他对象 : 一定不为空 - *
- * - * @param obj - * 任意对象 - * @return 是否为空 - */ - public static boolean isEmpty(Object obj) { - if (obj == null) - return true; - if (obj.getClass().isArray()) - return Array.getLength(obj) == 0; - if (obj instanceof Collection) - return ((Collection) obj).isEmpty(); - if (obj instanceof Map) - return ((Map) obj).isEmpty(); - return false; - } - - /** - * 判断一个数组是否是空数组 - * - * @param ary - * 数组 - * @return null 或者空数组都为 true 否则为 false - */ - public static boolean isEmptyArray(T[] ary) { - return null == ary || ary.length == 0; - } - - /** - * 较方便的创建一个列表,比如: - * - *
-     * List<Pet> pets = Lang.list(pet1, pet2, pet3);
-     * 
- * - * 注,这里的 List,是 ArrayList 的实例 - * - * @param eles - * 可变参数 - * @return 列表对象 - */ - public static ArrayList list(T... eles) { - ArrayList list = new ArrayList(eles.length); - for (T ele : eles) - list.add(ele); - return list; - } - - /** - * 创建一个 Hash 集合 - * - * @param eles - * 可变参数 - * @return 集合对象 - */ - public static Set set(T... eles) { - Set set = new HashSet(); - for (T ele : eles) - set.add(ele); - return set; - } - - /** - * 将多个数组,合并成一个数组。如果这些数组为空,则返回 null - * - * @param arys - * 数组对象 - * @return 合并后的数组对象 - */ - @SuppressWarnings("unchecked") - public static T[] merge(T[]... arys) { - Queue list = new LinkedList(); - for (T[] ary : arys) - if (null != ary) - for (T e : ary) - if (null != e) - list.add(e); - if (list.isEmpty()) - return null; - Class type = (Class) list.peek().getClass(); - return list.toArray((T[]) Array.newInstance(type, list.size())); - } - - /** - * 将一个对象添加成为一个数组的第一个元素,从而生成一个新的数组 - * - * @param e - * 对象 - * @param eles - * 数组 - * @return 新数组 - */ - @SuppressWarnings("unchecked") - public static T[] arrayFirst(T e, T[] eles) { - try { - if (null == eles || eles.length == 0) { - T[] arr = (T[]) Array.newInstance(e.getClass(), 1); - arr[0] = e; - return arr; - } - T[] arr = (T[]) Array.newInstance(eles.getClass().getComponentType(), eles.length + 1); - arr[0] = e; - for (int i = 0; i < eles.length; i++) { - arr[i + 1] = eles[i]; - } - return arr; - } - catch (NegativeArraySizeException e1) { - throw Lang.wrapThrow(e1); - } - } - - /** - * 将一个对象添加成为一个数组的最后一个元素,从而生成一个新的数组 - * - * @param e - * 对象 - * @param eles - * 数组 - * @return 新数组 - */ - @SuppressWarnings("unchecked") - public static T[] arrayLast(T[] eles, T e) { - try { - if (null == eles || eles.length == 0) { - T[] arr = (T[]) Array.newInstance(e.getClass(), 1); - arr[0] = e; - return arr; - } - T[] arr = (T[]) Array.newInstance(eles.getClass().getComponentType(), eles.length + 1); - for (int i = 0; i < eles.length; i++) { - arr[i] = eles[i]; - } - arr[eles.length] = e; - return arr; - } - catch (NegativeArraySizeException e1) { - throw Lang.wrapThrow(e1); - } - } - - /** - * 将一个数组转换成字符串 - *

- * 所有的元素都被格式化字符串包裹。 这个格式话字符串只能有一个占位符, %s, %d 等,均可,请视你的数组内容而定 - * - * @param fmt - * 格式 - * @param objs - * 数组 - * @return 拼合后的字符串 - */ - public static StringBuilder concatBy(String fmt, T[] objs) { - StringBuilder sb = new StringBuilder(); - for (T obj : objs) - sb.append(String.format(fmt, obj)); - return sb; - } - - /** - * 将一个数组转换成字符串 - *

- * 所有的元素都被格式化字符串包裹。 这个格式话字符串只能有一个占位符, %s, %d 等,均可,请视你的数组内容而定 - *

- * 每个元素之间,都会用一个给定的字符分隔 - * - * @param ptn - * 格式 - * @param c - * 分隔符 - * @param objs - * 数组 - * @return 拼合后的字符串 - */ - public static StringBuilder concatBy(String ptn, Object c, T[] objs) { - StringBuilder sb = new StringBuilder(); - for (T obj : objs) - sb.append(String.format(ptn, obj)).append(c); - if (sb.length() > 0) - sb.deleteCharAt(sb.length() - 1); - return sb; - } - - /** - * 将一个数组转换成字符串 - *

- * 每个元素之间,都会用一个给定的字符分隔 - * - * @param c - * 分隔符 - * @param objs - * 数组 - * @return 拼合后的字符串 - */ - public static StringBuilder concat(Object c, T[] objs) { - StringBuilder sb = new StringBuilder(); - if (null == objs || 0 == objs.length) - return sb; - - sb.append(objs[0]); - for (int i = 1; i < objs.length; i++) - sb.append(c).append(objs[i]); - - return sb; - } - - /** - * 清除数组中的特定值 - * - * @param objs - * 数组 - * @param val - * 值,可以是 null,如果是对象,则会用 equals 来比较 - * @return 新的数组实例 - */ - @SuppressWarnings("unchecked") - public static T[] without(T[] objs, T val) { - if (null == objs || objs.length == 0) { - return objs; - } - List list = new ArrayList(objs.length); - Class eleType = null; - for (T obj : objs) { - if (obj == val || (null != obj && null != val && obj.equals(val))) - continue; - if (null == eleType && obj != null) - eleType = obj.getClass(); - list.add(obj); - } - if (list.isEmpty()) { - return (T[]) new Object[0]; - } - return list.toArray((T[]) Array.newInstance(eleType, list.size())); - } - - /** - * 将一个长整型数组转换成字符串 - *

- * 每个元素之间,都会用一个给定的字符分隔 - * - * @param c - * 分隔符 - * @param vals - * 数组 - * @return 拼合后的字符串 - */ - public static StringBuilder concat(Object c, long[] vals) { - StringBuilder sb = new StringBuilder(); - if (null == vals || 0 == vals.length) - return sb; - - sb.append(vals[0]); - for (int i = 1; i < vals.length; i++) - sb.append(c).append(vals[i]); - - return sb; - } - - /** - * 将一个整型数组转换成字符串 - *

- * 每个元素之间,都会用一个给定的字符分隔 - * - * @param c - * 分隔符 - * @param vals - * 数组 - * @return 拼合后的字符串 - */ - public static StringBuilder concat(Object c, int[] vals) { - StringBuilder sb = new StringBuilder(); - if (null == vals || 0 == vals.length) - return sb; - - sb.append(vals[0]); - for (int i = 1; i < vals.length; i++) - sb.append(c).append(vals[i]); - - return sb; - } - - /** - * 将一个数组的部分元素转换成字符串 - *

- * 每个元素之间,都会用一个给定的字符分隔 - * - * @param offset - * 开始元素的下标 - * @param len - * 元素数量 - * @param c - * 分隔符 - * @param objs - * 数组 - * @return 拼合后的字符串 - */ - public static StringBuilder concat(int offset, int len, Object c, T[] objs) { - StringBuilder sb = new StringBuilder(); - if (null == objs || len < 0 || 0 == objs.length) - return sb; - - if (offset < objs.length) { - sb.append(objs[offset]); - for (int i = 1; i < len && i + offset < objs.length; i++) { - sb.append(c).append(objs[i + offset]); - } - } - return sb; - } - - /** - * 将一个数组所有元素拼合成一个字符串 - * - * @param objs - * 数组 - * @return 拼合后的字符串 - */ - public static StringBuilder concat(T[] objs) { - StringBuilder sb = new StringBuilder(); - for (T e : objs) - sb.append(e.toString()); - return sb; - } - - /** - * 将一个数组部分元素拼合成一个字符串 - * - * @param offset - * 开始元素的下标 - * @param len - * 元素数量 - * @param array - * 数组 - * @return 拼合后的字符串 - */ - public static StringBuilder concat(int offset, int len, T[] array) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < len; i++) { - sb.append(array[i + offset].toString()); - } - return sb; - } - - /** - * 将一个集合转换成字符串 - *

- * 每个元素之间,都会用一个给定的字符分隔 - * - * @param c - * 分隔符 - * @param coll - * 集合 - * @return 拼合后的字符串 - */ - public static StringBuilder concat(Object c, Collection coll) { - StringBuilder sb = new StringBuilder(); - if (null == coll || coll.isEmpty()) - return sb; - return concat(c, coll.iterator()); - } - - /** - * 将一个迭代器转换成字符串 - *

- * 每个元素之间,都会用一个给定的字符分隔 - * - * @param c - * 分隔符 - * @param it - * 集合 - * @return 拼合后的字符串 - */ - public static StringBuilder concat(Object c, Iterator it) { - StringBuilder sb = new StringBuilder(); - if (it == null || !it.hasNext()) - return sb; - sb.append(it.next()); - while (it.hasNext()) - sb.append(c).append(it.next()); - return sb; - } - - /** - * 将一个或者多个数组填入一个集合。 - * - * @param - * 集合类型 - * @param - * 数组元素类型 - * @param coll - * 集合 - * @param objss - * 数组 (数目可变) - * @return 集合对象 - */ - public static , T> C fill(C coll, T[]... objss) { - for (T[] objs : objss) - for (T obj : objs) - coll.add(obj); - return coll; - } - - /** - * 将一个集合变成 Map。 - * - * @param mapClass - * Map 的类型 - * @param coll - * 集合对象 - * @param keyFieldName - * 采用集合中元素的哪个一个字段为键。 - * @return Map 对象 - */ - public static > T collection2map(Class mapClass, - Collection coll, - String keyFieldName) { - if (null == coll) - return null; - T map = createMap(mapClass); - if (coll.size() > 0) { - Iterator it = coll.iterator(); - Object obj = it.next(); - Mirror mirror = Mirror.me(obj.getClass()); - Object key = mirror.getValue(obj, keyFieldName); - map.put(key, obj); - for (; it.hasNext();) { - obj = it.next(); - key = mirror.getValue(obj, keyFieldName); - map.put(key, obj); - } - } - return (T) map; - } - - /** - * 将集合变成 ArrayList - * - * @param col - * 集合对象 - * @return 列表对象 - */ - @SuppressWarnings("unchecked") - public static List collection2list(Collection col) { - if (null == col) - return null; - if (col.size() == 0) - return new ArrayList(0); - Class eleType = (Class) col.iterator().next().getClass(); - return collection2list(col, eleType); - } - - /** - * 将集合编程变成指定类型的列表 - * - * @param col - * 集合对象 - * @param eleType - * 列表类型 - * @return 列表对象 - */ - public static List collection2list(Collection col, Class eleType) { - if (null == col) - return null; - List list = new ArrayList(col.size()); - for (Object obj : col) - list.add(Castors.me().castTo(obj, eleType)); - return list; - } - - /** - * 将集合变成数组,数组的类型为集合的第一个元素的类型。如果集合为空,则返回 null - * - * @param coll - * 集合对象 - * @return 数组 - */ - @SuppressWarnings("unchecked") - public static E[] collection2array(Collection coll) { - if (null == coll) - return null; - if (coll.size() == 0) - return (E[]) new Object[0]; - - Class eleType = (Class) Lang.first(coll).getClass(); - return collection2array(coll, eleType); - } - - /** - * 将集合变成指定类型的数组 - * - * @param col - * 集合对象 - * @param eleType - * 数组元素类型 - * @return 数组 - */ - @SuppressWarnings("unchecked") - public static E[] collection2array(Collection col, Class eleType) { - if (null == col) - return null; - Object re = Array.newInstance(eleType, col.size()); - int i = 0; - for (Iterator it = col.iterator(); it.hasNext();) { - Object obj = it.next(); - if (null == obj) - Array.set(re, i++, null); - else - Array.set(re, i++, Castors.me().castTo(obj, eleType)); - } - return (E[]) re; - } - - /** - * 将一个数组变成 Map - * - * @param mapClass - * Map 的类型 - * @param array - * 数组 - * @param keyFieldName - * 采用集合中元素的哪个一个字段为键。 - * @return Map 对象 - */ - public static > T array2map(Class mapClass, - Object array, - String keyFieldName) { - if (null == array) - return null; - T map = createMap(mapClass); - int len = Array.getLength(array); - if (len > 0) { - Object obj = Array.get(array, 0); - Mirror mirror = Mirror.me(obj.getClass()); - for (int i = 0; i < len; i++) { - obj = Array.get(array, i); - Object key = mirror.getValue(obj, keyFieldName); - map.put(key, obj); - } - } - return map; - } - - @SuppressWarnings("unchecked") - private static > T createMap(Class mapClass) { - T map; - try { - map = mapClass.newInstance(); - } - catch (Exception e) { - map = (T) new HashMap(); - } - if (!mapClass.isAssignableFrom(map.getClass())) { - throw Lang.makeThrow("Fail to create map [%s]", mapClass.getName()); - } - return map; - } - - /** - * 将数组转换成一个列表。 - * - * @param array - * 原始数组 - * @return 新列表 - * - * @see org.nutz.castor.Castors - */ - public static List array2list(T[] array) { - if (null == array) - return null; - List re = new ArrayList(array.length); - for (T obj : array) - re.add(obj); - return re; - } - - /** - * 将数组转换成一个列表。将会采用 Castor 来深层转换数组元素 - * - * @param array - * 原始数组 - * @param eleType - * 新列表的元素类型 - * @return 新列表 - * - * @see org.nutz.castor.Castors - */ - public static List array2list(Object array, Class eleType) { - if (null == array) - return null; - int len = Array.getLength(array); - List re = new ArrayList(len); - for (int i = 0; i < len; i++) { - Object obj = Array.get(array, i); - re.add(Castors.me().castTo(obj, eleType)); - } - return re; - } - - /** - * 将数组转换成另外一种类型的数组。将会采用 Castor 来深层转换数组元素 - * - * @param array - * 原始数组 - * @param eleType - * 新数组的元素类型 - * @return 新数组 - * @throws FailToCastObjectException - * - * @see org.nutz.castor.Castors - */ - public static Object array2array(Object array, Class eleType) - throws FailToCastObjectException { - if (null == array) - return null; - int len = Array.getLength(array); - Object re = Array.newInstance(eleType, len); - for (int i = 0; i < len; i++) { - Array.set(re, i, Castors.me().castTo(Array.get(array, i), eleType)); - } - return re; - } - - /** - * 将数组转换成Object[] 数组。将会采用 Castor 来深层转换数组元素 - * - * @param args - * 原始数组 - * @param pts - * 新数组的元素类型 - * @return 新数组 - * @throws FailToCastObjectException - * - * @see org.nutz.castor.Castors - */ - public static Object[] array2ObjectArray(T[] args, Class[] pts) - throws FailToCastObjectException { - if (null == args) - return null; - Object[] newArgs = new Object[args.length]; - for (int i = 0; i < args.length; i++) { - newArgs[i] = Castors.me().castTo(args[i], pts[i]); - } - return newArgs; - } - - /** - * 根据一个 Map,和给定的对象类型,创建一个新的 JAVA 对象 - * - * @param src - * Map 对象 - * @param toType - * JAVA 对象类型 - * @return JAVA 对象 - * @throws FailToCastObjectException - */ - @SuppressWarnings({"unchecked", "rawtypes"}) - public static T map2Object(Map src, Class toType) - throws FailToCastObjectException { - if (null == toType) - throw new FailToCastObjectException("target type is Null"); - // 类型相同 - if (toType == Map.class) - return (T) src; - // 也是一种 Map - if (Map.class.isAssignableFrom(toType)) { - Map map; - try { - map = (Map) toType.newInstance(); - map.putAll(src); - return (T) map; - } - catch (Exception e) { - throw new FailToCastObjectException("target type fail to born!", unwrapThrow(e)); - } - - } - // 数组 - if (toType.isArray()) - return (T) Lang.collection2array(src.values(), toType.getComponentType()); - // List - if (List.class == toType) { - return (T) Lang.collection2list(src.values()); - } - - // POJO - Mirror mirror = Mirror.me(toType); - T obj = mirror.born(); - for (Field field : mirror.getFields()) { - Object v = null; - if (!Lang.isAndroid && field.isAnnotationPresent(Column.class)) { - String cv = field.getAnnotation(Column.class).value(); - v = src.get(cv); - } - - if (null == v && src.containsKey(field.getName())) { - v = src.get(field.getName()); - } - - if (null != v) { - //Class ft = field.getType(); - //获取泛型基类中的字段真实类型, https://github.com/nutzam/nutz/issues/1288 - Class ft = ReflectTool.getGenericFieldType(toType, field); - Object vv = null; - // 集合 - if (v instanceof Collection) { - Collection c = (Collection) v; - // 集合到数组 - if (ft.isArray()) { - vv = Lang.collection2array(c, ft.getComponentType()); - } - // 集合到集合 - else { - // 创建 - Collection newCol; - //Class eleType = Mirror.getGenericTypes(field, 0); - Class eleType = ReflectTool.getParameterRealGenericClass(toType, - field.getGenericType(),0); - if (ft == List.class) { - newCol = new ArrayList(c.size()); - } else if (ft == Set.class) { - newCol = new LinkedHashSet(); - } else { - try { - newCol = (Collection) ft.newInstance(); - } - catch (Exception e) { - throw Lang.wrapThrow(e); - } - } - // 赋值 - for (Object ele : c) { - newCol.add(Castors.me().castTo(ele, eleType)); - } - vv = newCol; - } - } - // Map - else if (v instanceof Map && Map.class.isAssignableFrom(ft)) { - // 创建 - final Map map; - // Map 接口 - if (ft == Map.class) { - map = new HashMap(); - } - // 自己特殊的 Map - else { - try { - map = (Map) ft.newInstance(); - } - catch (Exception e) { - throw new FailToCastObjectException("target type fail to born!", e); - } - } - // 赋值 - //final Class valType = Mirror.getGenericTypes(field, 1); - //map的key和value字段类型 - final Class keyType = ReflectTool.getParameterRealGenericClass(toType, - field.getGenericType(),0); - final Class valType =ReflectTool.getParameterRealGenericClass(toType, - field.getGenericType(),1); - each(v, new Each() { - public void invoke(int i, Entry en, int length) { - map.put(Castors.me().castTo(en.getKey(), keyType), - Castors.me().castTo(en.getValue(), valType)); - } - }); - vv = map; - } - // 强制转换 - else { - vv = Castors.me().castTo(v, ft); - } - mirror.setValue(obj, field, vv); - } - } - return obj; - } - - /** - * 根据一段字符串,生成一个 Map 对象。 - * - * @param str - * 参照 JSON 标准的字符串,但是可以没有前后的大括号 - * @return Map 对象 - */ - public static NutMap map(String str) { - if (null == str) - return null; - str = Strings.trim(str); - if (!Strings.isEmpty(str) - && (Strings.isQuoteBy(str, '{', '}') || Strings.isQuoteBy(str, '(', ')'))) { - return Json.fromJson(NutMap.class, str); - } - return Json.fromJson(NutMap.class, "{" + str + "}"); - } - - /** - * 将一个 Map 所有的键都按照回调进行修改 - * - * 本函数遇到数组或者集合,会自动处理每个元素 - * - * @param obj - * 要转换的 Map 或者 集合或者数组 - * - * @param mkc - * 键值修改的回调 - * @param recur - * 遇到 Map 是否递归 - * - * @see MapKeyConvertor - */ - @SuppressWarnings("unchecked") - public static void convertMapKey(Object obj, MapKeyConvertor mkc, boolean recur) { - // Map - if (obj instanceof Map) { - Map map = (Map) obj; - NutMap map2 = new NutMap(); - for (Map.Entry en : map.entrySet()) { - String key = en.getKey(); - Object val = en.getValue(); - - if (recur) - convertMapKey(val, mkc, recur); - - String newKey = mkc.convertKey(key); - map2.put(newKey, val); - } - map.clear(); - map.putAll(map2); - } - // Collection - else if (obj instanceof Collection) { - for (Object ele : (Collection) obj) { - convertMapKey(ele, mkc, recur); - } - } - // Array - else if (obj.getClass().isArray()) { - for (Object ele : (Object[]) obj) { - convertMapKey(ele, mkc, recur); - } - } - } - - /** - * 创建一个一个键的 Map 对象 - * - * @param key - * 键 - * @param v - * 值 - * @return Map 对象 - */ - public static NutMap map(String key, Object v) { - return new NutMap().addv(key, v); - } - - /** - * 根据一个格式化字符串,生成 Map 对象 - * - * @param fmt - * 格式化字符串 - * @param args - * 字符串参数 - * @return Map 对象 - */ - public static NutMap mapf(String fmt, Object... args) { - return map(String.format(fmt, args)); - } - - /** - * 创建一个新的上下文对象 - * - * @return 一个新创建的上下文对象 - */ - public static Context context() { - return new SimpleContext(); - } - - /** - * 根据一个 Map 包裹成一个上下文对象 - * - * @param map - * Map 对象 - * - * @return 一个新创建的上下文对象 - */ - public static Context context(Map map) { - return new SimpleContext(map); - } - - /** - * 根据一段 JSON 字符串,生产一个新的上下文对象 - * - * @param fmt - * JSON 字符串模板 - * @param args - * 模板参数 - * - * @return 一个新创建的上下文对象 - */ - public static Context contextf(String fmt, Object... args) { - return context(Lang.mapf(fmt, args)); - } - - /** - * 根据一段 JSON 字符串,生产一个新的上下文对象 - * - * @return 一个新创建的上下文对象 - */ - public static Context context(String str) { - return context(map(str)); - } - - /** - * 根据一段字符串,生成一个List 对象。 - * - * @param str - * 参照 JSON 标准的字符串,但是可以没有前后的中括号 - * @return List 对象 - */ - @SuppressWarnings("unchecked") - public static List list4(String str) { - if (null == str) - return null; - if ((str.length() > 0 && str.charAt(0) == '[') && str.endsWith("]")) - return (List) Json.fromJson(str); - return (List) Json.fromJson("[" + str + "]"); - } - - /** - * 获得一个对象的长度。它可以接受: - *
    - *
  • null : 0 - *
  • 数组 - *
  • 集合 - *
  • Map - *
  • 一般 Java 对象。 返回 1 - *
- * 如果你想让你的 Java 对象返回不是 1 , 请在对象中声明 length() 函数 - * - * @param obj - * @return 对象长度 - * @deprecated 这玩意很脑残,为啥最后要动态调个 "length",导致字符串类很麻烦,以后用 Lang.eleSize 函数代替吧 - */ - @Deprecated - public static int length(Object obj) { - if (null == obj) - return 0; - if (obj.getClass().isArray()) { - return Array.getLength(obj); - } else if (obj instanceof Collection) { - return ((Collection) obj).size(); - } else if (obj instanceof Map) { - return ((Map) obj).size(); - } - try { - return (Integer) Mirror.me(obj.getClass()).invoke(obj, "length"); - } - catch (Exception e) {} - return 1; - } - - /** - * 获得一个容器(Map/集合/数组)对象包含的元素数量 - *
    - *
  • null : 0 - *
  • 数组 - *
  • 集合 - *
  • Map - *
  • 一般 Java 对象。 返回 1 - *
- * - * @param obj - * @return 对象长度 - * @since Nutz 1.r.62 - */ - public static int eleSize(Object obj) { - // 空指针,就是 0 - if (null == obj) - return 0; - // 数组 - if (obj.getClass().isArray()) { - return Array.getLength(obj); - } - // 容器 - if (obj instanceof Collection) { - return ((Collection) obj).size(); - } - // Map - if (obj instanceof Map) { - return ((Map) obj).size(); - } - // 其他的就是 1 咯 - return 1; - } - - /** - * 如果是数组或集合取得第一个对象。 否则返回自身 - * - * @param obj - * 任意对象 - * @return 第一个代表对象 - */ - public static Object first(Object obj) { - if (null == obj) - return obj; - - if (obj instanceof Collection) { - Iterator it = ((Collection) obj).iterator(); - return it.hasNext() ? it.next() : null; - } - - if (obj.getClass().isArray()) - return Array.getLength(obj) > 0 ? Array.get(obj, 0) : null; - - return obj; - } - - /** - * 获取集合中的第一个元素,如果集合为空,返回 null - * - * @param coll - * 集合 - * @return 第一个元素 - */ - public static T first(Collection coll) { - if (null == coll || coll.isEmpty()) - return null; - return coll.iterator().next(); - } - - /** - * 获得表中的第一个名值对 - * - * @param map - * 表 - * @return 第一个名值对 - */ - public static Entry first(Map map) { - if (null == map || map.isEmpty()) - return null; - return map.entrySet().iterator().next(); - } - - /** - * 打断 each 循环 - */ - public static void Break() throws ExitLoop { - throw new ExitLoop(); - } - - /** - * 继续 each 循环,如果再递归,则停止递归 - */ - public static void Continue() throws ExitLoop { - throw new ContinueLoop(); - } - - /** - * 用回调的方式,遍历一个对象,可以支持遍历 - *
    - *
  • 数组 - *
  • 集合 - *
  • Map - *
  • 单一元素 - *
- * - * @param obj - * 对象 - * @param callback - * 回调 - */ - public static void each(Object obj, Each callback) { - each(obj, true, callback); - } - - /** - * 用回调的方式,遍历一个对象,可以支持遍历 - *
    - *
  • 数组 - *
  • 集合 - *
  • Map - *
  • 单一元素 - *
- * - * @param obj - * 对象 - * @param loopMap - * 是否循环 Map,如果循环 Map 则主要看 callback 的 T,如果是 Map.Entry 则循环 Entry - * 否循环 value。如果本值为 false, 则将 Map 当作一个完整的对象来看待 - * @param callback - * 回调 - */ - @SuppressWarnings({"rawtypes", "unchecked"}) - public static void each(Object obj, boolean loopMap, Each callback) { - if (null == obj || null == callback) - return; - try { - // 循环开始 - if (callback instanceof Loop) - if (!((Loop) callback).begin()) - return; - - // 进行循环 - if (obj.getClass().isArray()) { - int len = Array.getLength(obj); - for (int i = 0; i < len; i++) - try { - callback.invoke(i, (T) Array.get(obj, i), len); - } - catch (ContinueLoop e) {} - catch (ExitLoop e) { - break; - } - } else if (obj instanceof Collection) { - int len = ((Collection) obj).size(); - int i = 0; - for (Iterator it = ((Collection) obj).iterator(); it.hasNext();) - try { - callback.invoke(i++, it.next(), len); - } - catch (ContinueLoop e) {} - catch (ExitLoop e) { - break; - } - } else if (loopMap && obj instanceof Map) { - Map map = (Map) obj; - int len = map.size(); - int i = 0; - Class eType = Mirror.getTypeParam(callback.getClass(), 0); - if (null != eType && eType != Object.class && eType.isAssignableFrom(Entry.class)) { - for (Object v : map.entrySet()) - try { - callback.invoke(i++, (T) v, len); - } - catch (ContinueLoop e) {} - catch (ExitLoop e) { - break; - } - - } else { - for (Object v : map.entrySet()) - try { - callback.invoke(i++, (T) ((Entry) v).getValue(), len); - } - catch (ContinueLoop e) {} - catch (ExitLoop e) { - break; - } - } - } else if (obj instanceof Iterator) { - Iterator it = (Iterator) obj; - int i = 0; - while (it.hasNext()) { - try { - callback.invoke(i++, (T) it.next(), -1); - } - catch (ContinueLoop e) {} - catch (ExitLoop e) { - break; - } - } - } else - try { - callback.invoke(0, (T) obj, 1); - } - catch (ContinueLoop e) {} - catch (ExitLoop e) {} - - // 循环结束 - if (callback instanceof Loop) - ((Loop) callback).end(); - } - catch (LoopException e) { - throw Lang.wrapThrow(e.getCause()); - } - } - - /** - * 安全的从一个数组获取一个元素,容忍 null 数组,以及支持负数的 index - *

- * 如果该下标越界,则返回 null - * - * @param - * @param array - * 数组,如果为 null 则直接返回 null - * @param index - * 下标,-1 表示倒数第一个, -2 表示倒数第二个,以此类推 - * @return 数组元素 - */ - public static T get(T[] array, int index) { - if (null == array) - return null; - int i = index < 0 ? array.length + index : index; - if (i < 0 || i >= array.length) - return null; - return array[i]; - } - - /** - * 将一个抛出对象的异常堆栈,显示成一个字符串 - * - * @param e - * 抛出对象 - * @return 异常堆栈文本 - */ - public static String getStackTrace(Throwable e) { - StringBuilder sb = new StringBuilder(); - StringOutputStream sbo = new StringOutputStream(sb); - PrintStream ps = new PrintStream(sbo); - e.printStackTrace(ps); - ps.flush(); - return sbo.getStringBuilder().toString(); - } - - /** - * 将字符串解析成 boolean 值,支持更多的字符串 - *

    - *
  • 1 | 0 - *
  • yes | no - *
  • on | off - *
  • true | false - *
- * - * @param s - * 字符串 - * @return 布尔值 - */ - public static boolean parseBoolean(String s) { - if (null == s || s.length() == 0) - return false; - if (s.length() > 5) - return true; - if ("0".equals(s)) - return false; - s = s.toLowerCase(); - return !"false".equals(s) && !"off".equals(s) && !"no".equals(s); - } - - /** - * 帮你快速获得一个 DocumentBuilder,方便 XML 解析。 - * - * @return 一个 DocumentBuilder 对象 - * @throws ParserConfigurationException - */ - public static DocumentBuilder xmls() throws ParserConfigurationException { - return Xmls.xmls(); - } - - /** - * 对Thread.sleep(long)的简单封装,不抛出任何异常 - * - * @param millisecond - * 休眠时间 - */ - public static void quiteSleep(long millisecond) { - try { - if (millisecond > 0) - Thread.sleep(millisecond); - } - catch (Throwable e) {} - } - - /** - * 将字符串,变成数字对象,现支持的格式为: - *
    - *
  • null - 整数 0
  • - *
  • 23.78 - 浮点 Float
  • - *
  • 0x45 - 16进制整数 Integer
  • - *
  • 78L - 长整数 Long
  • - *
  • 69 - 普通整数 Integer
  • - *
- * - * @param s - * 参数 - * @return 数字对象 - */ - public static Number str2number(String s) { - // null 值 - if (null == s) { - return 0; - } - s = s.toUpperCase(); - // 浮点 - if (s.indexOf('.') != -1) { - char c = s.charAt(s.length() - 1); - if (c == 'F' || c == 'f') { - return Float.valueOf(s); - } - return Double.valueOf(s); - } - // 16进制整数 - if (s.startsWith("0X")) { - return Integer.valueOf(s.substring(2), 16); - } - // 长整数 - if (s.charAt(s.length() - 1) == 'L' || s.charAt(s.length() - 1) == 'l') { - return Long.valueOf(s.substring(0, s.length() - 1)); - } - // 普通整数 - Long re = Long.parseLong(s); - if (Integer.MAX_VALUE >= re && re >= Integer.MIN_VALUE) - return re.intValue(); - return re; - } - - @SuppressWarnings("unchecked") - private static > void obj2map(Object obj, - T map, - final Map memo) { - // 已经转换过了,不要递归转换 - if (null == obj || memo.containsKey(obj)) - return; - memo.put(obj, ""); - - // Fix issue #497 - // 如果是 Map,就直接 putAll 一下咯 - if (obj instanceof Map) { - map.putAll(__change_map_to_nutmap((Map) obj, memo)); - return; - } - - // 下面是普通的 POJO - Mirror mirror = Mirror.me(obj.getClass()); - Field[] flds = mirror.getFields(); - for (Field fld : flds) { - Object v = mirror.getValue(obj, fld); - if (null == v) { - continue; - } - Mirror mr = Mirror.me(v); - // 普通值 - if (mr.isSimple()) { - map.put(fld.getName(), v); - } - // 已经输出过了 - else if (memo.containsKey(v)) { - map.put(fld.getName(), null); - } - // 数组或者集合 - else if (mr.isColl()) { - final List list = new ArrayList(Lang.length(v)); - Lang.each(v, new Each() { - public void invoke(int index, Object ele, int length) { - __join_ele_to_list_as_map(list, ele, memo); - } - }); - map.put(fld.getName(), list); - } - // Map - else if (mr.isMap()) { - NutMap map2 = __change_map_to_nutmap((Map) v, memo); - map.put(fld.getName(), map2); - } - // 看来要递归 - else { - T sub; - try { - sub = (T) map.getClass().newInstance(); - } - catch (Exception e) { - throw Lang.wrapThrow(e); - } - obj2map(v, sub, memo); - map.put(fld.getName(), sub); - } - } - } - - @SuppressWarnings("unchecked") - private static NutMap __change_map_to_nutmap(Map map, - final Map memo) { - NutMap re = new NutMap(); - for (Map.Entry en : map.entrySet()) { - Object v = en.getValue(); - if (null == v) - continue; - Mirror mr = Mirror.me(v); - // 普通值 - if (mr.isSimple()) { - re.put(en.getKey(), v); - } - // 已经输出过了 - else if (memo.containsKey(v)) { - continue; - } - // 数组或者集合 - else if (mr.isColl()) { - final List list2 = new ArrayList(Lang.length(v)); - Lang.each(v, new Each() { - public void invoke(int index, Object ele, int length) { - __join_ele_to_list_as_map(list2, ele, memo); - } - }); - re.put(en.getKey(), list2); - } - // Map - else if (mr.isMap()) { - NutMap map2 = __change_map_to_nutmap((Map) v, memo); - re.put(en.getKey(), map2); - } - // 看来要递归 - else { - NutMap map2 = obj2nutmap(v); - re.put(en.getKey(), map2); - } - } - return re; - } - - @SuppressWarnings("unchecked") - private static void __join_ele_to_list_as_map(List list, - Object o, - final Map memo) { - if (null == o) { - return; - } - - // 如果是 Map,就直接 putAll 一下咯 - if (o instanceof Map) { - NutMap map2 = __change_map_to_nutmap((Map) o, memo); - list.add(map2); - return; - } - - Mirror mr = Mirror.me(o); - // 普通值 - if (mr.isSimple()) { - list.add(o); - } - // 已经输出过了 - else if (memo.containsKey(o)) { - list.add(null); - } - // 数组或者集合 - else if (mr.isColl()) { - final List list2 = new ArrayList(Lang.length(o)); - Lang.each(o, new Each() { - public void invoke(int index, Object ele, int length) { - __join_ele_to_list_as_map(list2, ele, memo); - } - }); - list.add(list2); - } - // Map - else if (mr.isMap()) { - NutMap map2 = __change_map_to_nutmap((Map) o, memo); - list.add(map2); - } - // 看来要递归 - else { - NutMap map = obj2nutmap(o); - list.add(map); - } - } - - /** - * 将对象转换成 Map - * - * @param obj - * POJO 对象 - * @return Map 对象 - */ - @SuppressWarnings("unchecked") - public static Map obj2map(Object obj) { - return obj2map(obj, HashMap.class); - } - - /** - * 将对象转为 Nutz 的标准 Map 封装 - * - * @param obj - * POJO du对象 - * @return NutMap 对象 - */ - public static NutMap obj2nutmap(Object obj) { - return obj2map(obj, NutMap.class); - } - - /** - * 将对象转换成 Map - * - * @param - * @param obj - * POJO 对象 - * @param mapType - * Map 的类型 - * @return Map 对象 - */ - public static > T obj2map(Object obj, Class mapType) { - try { - T map = mapType.newInstance(); - Lang.obj2map(obj, map, new HashMap()); - return map; - } - catch (Exception e) { - throw Lang.wrapThrow(e); - } - } - - /** - * 返回一个集合对象的枚举对象。实际上就是对 Iterator 接口的一个封装 - * - * @param col - * 集合对象 - * @return 枚举对象 - */ - public static Enumeration enumeration(Collection col) { - final Iterator it = col.iterator(); - return new Enumeration() { - public boolean hasMoreElements() { - return it.hasNext(); - } - - public T nextElement() { - return it.next(); - } - }; - } - - /** - * 将枚举对象,变成集合 - * - * @param enums - * 枚举对象 - * @param cols - * 集合对象 - * @return 集合对象 - */ - public static , E> T enum2collection(Enumeration enums, T cols) { - while (enums.hasMoreElements()) - cols.add(enums.nextElement()); - return cols; - } - - /** - * 将字符数组强制转换成字节数组。如果字符为双字节编码,则会丢失信息 - * - * @param cs - * 字符数组 - * @return 字节数组 - */ - public static byte[] toBytes(char[] cs) { - byte[] bs = new byte[cs.length]; - for (int i = 0; i < cs.length; i++) - bs[i] = (byte) cs[i]; - return bs; - } - - /** - * 将整数数组强制转换成字节数组。整数的高位将会被丢失 - * - * @param is - * 整数数组 - * @return 字节数组 - */ - public static byte[] toBytes(int[] is) { - byte[] bs = new byte[is.length]; - for (int i = 0; i < is.length; i++) - bs[i] = (byte) is[i]; - return bs; - } - - /** - * 判断当前系统是否为Windows - * - * @return true 如果当前系统为Windows系统 - */ - public static boolean isWin() { - try { - String os = System.getenv("OS"); - return os != null && os.indexOf("Windows") > -1; - } - catch (Throwable e) { - return false; - } - } - - /** - * 原方法使用线程ClassLoader,各种问题,改回原版. - */ - public static Class loadClass(String className) throws ClassNotFoundException { - try { - return Thread.currentThread().getContextClassLoader().loadClass(className); - } - catch (Throwable e) { - return Class.forName(className); - } - } - - /** - * 当前运行的 Java 虚拟机是 JDK6 及更高版本的话,则返回 true - * - * @return true 如果当前运行的 Java 虚拟机是 JDK6 - */ - public static boolean isJDK6() { - return JdkTool.getMajorVersion() >= 6; - } - - /** - * 获取基本类型的默认值 - * - * @param pClass - * 基本类型 - * @return 0/false,如果传入的pClass不是基本类型的类,则返回null - */ - public static Object getPrimitiveDefaultValue(Class pClass) { - if (int.class.equals(pClass)) - return Integer.valueOf(0); - if (long.class.equals(pClass)) - return Long.valueOf(0); - if (short.class.equals(pClass)) - return Short.valueOf((short) 0); - if (float.class.equals(pClass)) - return Float.valueOf(0f); - if (double.class.equals(pClass)) - return Double.valueOf(0); - if (byte.class.equals(pClass)) - return Byte.valueOf((byte) 0); - if (char.class.equals(pClass)) - return Character.valueOf((char) 0); - if (boolean.class.equals(pClass)) - return Boolean.FALSE; - return null; - } - - /** - * 当一个类使用来定义泛型时,本方法返回类的一个字段的具体类型。 - * - * @param me - * @param field - */ - public static Type getFieldType(Mirror me, String field) throws NoSuchFieldException { - return getFieldType(me, me.getField(field)); - } - - /** - * 当一个类使用 来定义泛型时, 本方法返回类的一个方法所有参数的具体类型 - * - * @param me - * @param method - */ - public static Type[] getMethodParamTypes(Mirror me, Method method) { - Type[] types = method.getGenericParameterTypes(); - List ts = new ArrayList(); - for (Type type : types) { - ts.add(getGenericsType(me, type)); - } - return ts.toArray(new Type[ts.size()]); - } - - /** - * 当一个类使用来定义泛型时,本方法返回类的一个字段的具体类型。 - * - * @param me - * @param field - */ - public static Type getFieldType(Mirror me, Field field) { - Type type = field.getGenericType(); - return getGenericsType(me, type); - } - - /** - * 当一个类使用来定义泛型时,本方法返回类的一个字段的具体类型。 - * - * @param me - * @param type - */ - public static Type getGenericsType(Mirror me, Type type) { - Type[] types = me.getGenericsTypes(); - Type t = type; - if (type instanceof TypeVariable && types != null && types.length > 0) { - Type[] tvs = me.getType().getTypeParameters(); - for (int i = 0; i < tvs.length; i++) { - if (type.equals(tvs[i])) { - type = me.getGenericsType(i); - break; - } - } - } - if (!type.equals(t)) { - return type; - } - if (types != null && types.length > 0 && type instanceof ParameterizedType) { - ParameterizedType pt = (ParameterizedType) type; - - if (pt.getActualTypeArguments().length >= 0) { - NutType nt = new NutType(); - nt.setOwnerType(pt.getOwnerType()); - nt.setRawType(pt.getRawType()); - Type[] tt = new Type[pt.getActualTypeArguments().length]; - for (int i = 0; i < tt.length; i++) { - tt[i] = types[i]; - } - nt.setActualTypeArguments(tt); - return nt; - } - } - - return type; - } - - /** - * 获取一个 Type 类型实际对应的Class - * - * @param type - * 类型 - * @return 与Type类型实际对应的Class - */ - @SuppressWarnings("rawtypes") - public static Class getTypeClass(Type type) { - Class clazz = null; - if (type instanceof Class) { - clazz = (Class) type; - } else if (type instanceof ParameterizedType) { - ParameterizedType pt = (ParameterizedType) type; - clazz = (Class) pt.getRawType(); - } else if (type instanceof GenericArrayType) { - GenericArrayType gat = (GenericArrayType) type; - Class typeClass = getTypeClass(gat.getGenericComponentType()); - return Array.newInstance(typeClass, 0).getClass(); - } else if (type instanceof TypeVariable) { - TypeVariable tv = (TypeVariable) type; - Type[] ts = tv.getBounds(); - if (ts != null && ts.length > 0) - return getTypeClass(ts[0]); - } else if (type instanceof WildcardType) { - WildcardType wt = (WildcardType) type; - Type[] t_low = wt.getLowerBounds();// 取其下界 - if (t_low.length > 0) - return getTypeClass(t_low[0]); - Type[] t_up = wt.getUpperBounds(); // 没有下界?取其上界 - return getTypeClass(t_up[0]);// 最起码有Object作为上界 - } - return clazz; - } - - /** - * 返回一个 Type 的泛型数组, 如果没有, 则直接返回null - * - * @param type - * 类型 - * @return 一个 Type 的泛型数组, 如果没有, 则直接返回null - */ - public static Type[] getGenericsTypes(Type type) { - if (type instanceof ParameterizedType) { - ParameterizedType pt = (ParameterizedType) type; - return pt.getActualTypeArguments(); - } - return null; - } - - /** - * 强制从字符串转换成一个 Class,将 ClassNotFoundException 包裹成 RuntimeException - * - * @param - * @param name - * 类名 - * @param type - * 这个类型的边界 - * @return 类对象 - */ - @SuppressWarnings("unchecked") - public static Class forName(String name, Class type) { - Class re; - try { - re = Lang.loadClass(name); - return (Class) re; - } - catch (ClassNotFoundException e) { - throw Lang.wrapThrow(e); - } - } - - /** - * 获取指定文件的 MD5 值 - * - * @param f - * 文件 - * @return 指定文件的 MD5 值 - * @see #digest(String, File) - */ - public static String md5(File f) { - return digest("MD5", f); - } - - /** - * 获取指定输入流的 MD5 值 - * - * @param ins - * 输入流 - * @return 指定输入流的 MD5 值 - * @see #digest(String, InputStream) - */ - public static String md5(InputStream ins) { - return digest("MD5", ins); - } - - /** - * 获取指定字符串的 MD5 值 - * - * @param cs - * 字符串 - * @return 指定字符串的 MD5 值 - * @see #digest(String, CharSequence) - */ - public static String md5(CharSequence cs) { - return digest("MD5", cs); - } - - /** - * 获取指定文件的 SHA1 值 - * - * @param f - * 文件 - * @return 指定文件的 SHA1 值 - * @see #digest(String, File) - */ - public static String sha1(File f) { - return digest("SHA1", f); - } - - /** - * 获取指定输入流的 SHA1 值 - * - * @param ins - * 输入流 - * @return 指定输入流的 SHA1 值 - * @see #digest(String, InputStream) - */ - public static String sha1(InputStream ins) { - return digest("SHA1", ins); - } - - /** - * 获取指定字符串的 SHA1 值 - * - * @param cs - * 字符串 - * @return 指定字符串的 SHA1 值 - * @see #digest(String, CharSequence) - */ - public static String sha1(CharSequence cs) { - return digest("SHA1", cs); - } - - /** - * 获取指定文件的 SHA256 值 - * - * @param f - * 文件 - * @return 指定文件的 SHA256 值 - * @see #digest(String, File) - */ - public static String sha256(File f) { - return digest("SHA-256", f); - } - - /** - * 获取指定输入流的 SHA256 值 - * - * @param ins - * 输入流 - * @return 指定输入流的 SHA256 值 - * @see #digest(String, InputStream) - */ - public static String sha256(InputStream ins) { - return digest("SHA-256", ins); - } - - /** - * 获取指定字符串的 SHA256 值 - * - * @param cs - * 字符串 - * @return 指定字符串的 SHA256 值 - * @see #digest(String, CharSequence) - */ - public static String sha256(CharSequence cs) { - return digest("SHA-256", cs); - } - - /** - * 从数据文件计算出数字签名 - * - * @param algorithm - * 算法,比如 "SHA1" "SHA-256" 或者 "MD5" 等 - * @param f - * 文件 - * @return 数字签名 - */ - public static String digest(String algorithm, File f) { - return digest(algorithm, Streams.fileIn(f)); - } - - /** - * 从流计算出数字签名,计算完毕流会被关闭 - * - * @param algorithm - * 算法,比如 "SHA1" 或者 "MD5" 等 - * @param ins - * 输入流 - * @return 数字签名 - */ - public static String digest(String algorithm, InputStream ins) { - try { - MessageDigest md = MessageDigest.getInstance(algorithm); - - byte[] bs = new byte[HASH_BUFF_SIZE]; - int len = 0; - while ((len = ins.read(bs)) != -1) { - md.update(bs, 0, len); - } - - byte[] hashBytes = md.digest(); - - return fixedHexString(hashBytes); - } - catch (NoSuchAlgorithmException e) { - throw Lang.wrapThrow(e); - } - catch (FileNotFoundException e) { - throw Lang.wrapThrow(e); - } - catch (IOException e) { - throw Lang.wrapThrow(e); - } - finally { - Streams.safeClose(ins); - } - } - - /** - * 从字符串计算出数字签名 - * - * @param algorithm - * 算法,比如 "SHA1" 或者 "MD5" 等 - * @param cs - * 字符串 - * @return 数字签名 - */ - public static String digest(String algorithm, CharSequence cs) { - return digest(algorithm, Strings.getBytesUTF8(null == cs ? "" : cs), null, 1); - } - - /** - * 从字节数组计算出数字签名 - * - * @param algorithm - * 算法,比如 "SHA1" 或者 "MD5" 等 - * @param bytes - * 字节数组 - * @param salt - * 随机字节数组 - * @param iterations - * 迭代次数 - * @return 数字签名 - */ - public static String digest(String algorithm, byte[] bytes, byte[] salt, int iterations) { - try { - MessageDigest md = MessageDigest.getInstance(algorithm); - - if (salt != null) { - md.update(salt); - } - - byte[] hashBytes = md.digest(bytes); - - for (int i = 1; i < iterations; i++) { - md.reset(); - hashBytes = md.digest(hashBytes); - } - - return fixedHexString(hashBytes); - } - catch (NoSuchAlgorithmException e) { - throw Lang.wrapThrow(e); - } - } - - /** 当前运行的 Java 虚拟机是否是在安卓环境 */ - public static final boolean isAndroid; - - static { - boolean flag = false; - try { - Class.forName("android.Manifest"); - flag = true; - } - catch (Throwable e) {} - isAndroid = flag; - } - - /** - * 将指定的数组的内容倒序排序。注意,这会破坏原数组的内容 - * - * @param arrays - * 指定的数组 - */ - public static void reverse(T[] arrays) { - int size = arrays.length; - for (int i = 0; i < size; i++) { - int ih = i; - int it = size - 1 - i; - if (ih == it || ih > it) { - break; - } - T ah = arrays[ih]; - T swap = arrays[it]; - arrays[ih] = swap; - arrays[it] = ah; - } - } - - @Deprecated - public static String simpleMetodDesc(Method method) { - return simpleMethodDesc(method); - } - - public static String simpleMethodDesc(Method method) { - return String.format("%s.%s(...)", - method.getDeclaringClass().getSimpleName(), - method.getName()); - } - - public static String fixedHexString(byte[] hashBytes) { - StringBuffer sb = new StringBuffer(); - for (int i = 0; i < hashBytes.length; i++) { - sb.append(Integer.toString((hashBytes[i] & 0xff) + 0x100, 16).substring(1)); - } - - return sb.toString(); - } - - /** - * 一个便利的方法,将当前线程睡眠一段时间 - * - * @param ms - * 要睡眠的时间 ms - */ - public static void sleep(long ms) { - try { - Thread.sleep(ms); - } - catch (InterruptedException e) { - throw Lang.wrapThrow(e); - } - } - - /** - * 一个便利的等待方法同步一个对象 - * - * @param lock - * 锁对象 - * @param ms - * 要等待的时间 ms - */ - public static void wait(Object lock, long ms) { - if (null != lock) - synchronized (lock) { - try { - lock.wait(ms); - } - catch (InterruptedException e) { - throw Lang.wrapThrow(e); - } - } - } - - /** - * 通知对象的同步锁 - * - * @param lock - * 锁对象 - */ - public static void notifyAll(Object lock) { - if (null != lock) - synchronized (lock) { - lock.notifyAll(); - } - } - - public static void runInAnThread(Runnable runnable) { - new Thread(runnable).start(); - } - - /** - * map对象浅过滤,返回值是一个新的map - * - * @param source - * 原始的map对象 - * @param prefix - * 包含什么前缀,并移除前缀 - * @param include - * 正则表达式 仅包含哪些key(如果有前缀要求,则已经移除了前缀) - * @param exclude - * 正则表达式 排除哪些key(如果有前缀要求,则已经移除了前缀) - * @param keyMap - * 映射map, 原始key--目标key (如果有前缀要求,则已经移除了前缀) - * @return 经过过滤的map,与原始map不是同一个对象 - */ - public static Map filter(Map source, - String prefix, - String include, - String exclude, - Map keyMap) { - LinkedHashMap dst = new LinkedHashMap(); - if (source == null || source.isEmpty()) - return dst; - - Pattern includePattern = include == null ? null : Pattern.compile(include); - Pattern excludePattern = exclude == null ? null : Pattern.compile(exclude); - - for (Entry en : source.entrySet()) { - String key = en.getKey(); - if (prefix != null) { - if (key.startsWith(prefix)) - key = key.substring(prefix.length()); - else - continue; - } - if (includePattern != null && !includePattern.matcher(key).find()) - continue; - if (excludePattern != null && excludePattern.matcher(key).find()) - continue; - if (keyMap != null && keyMap.containsKey(key)) - dst.put(keyMap.get(key), en.getValue()); - else - dst.put(key, en.getValue()); - } - return dst; - } - - /** - * 获得访问者的IP地址, 反向代理过的也可以获得 - * - * @param request - * 请求的req对象 - * @return 来源ip - */ - public static String getIP(HttpServletRequest request) { - if (request == null) - return ""; - String ip = request.getHeader("X-Forwarded-For"); - if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { - if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { - ip = request.getHeader("Proxy-Client-IP"); - } - if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { - ip = request.getHeader("WL-Proxy-Client-IP"); - } - if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { - ip = request.getHeader("HTTP_CLIENT_IP"); - } - if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { - ip = request.getHeader("HTTP_X_FORWARDED_FOR"); - } - if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { - ip = request.getRemoteAddr(); - } - } else if (ip.length() > 15) { - String[] ips = ip.split(","); - for (int index = 0; index < ips.length; index++) { - String strIp = ips[index]; - if (!("unknown".equalsIgnoreCase(strIp))) { - ip = strIp; - break; - } - } - } - if (Strings.isBlank(ip)) - return ""; - if (isIPv4Address(ip) || isIPv6Address(ip)) { - return ip; - } - return ""; - } - - /** - * @return 返回当前程序运行的根目录 - */ - public static String runRootPath() { - String cp = Lang.class.getClassLoader().getResource("").toExternalForm(); - if (cp.startsWith("file:")) { - cp = cp.substring("file:".length()); - } - return cp; - } - - public static T copyProperties(Object origin, T target) { - return copyProperties(origin, target, null, null, false, true); - } - - public static T copyProperties(Object origin, - T target, - String active, - String lock, - boolean ignoreNull, - boolean ignoreStatic) { - if (origin == null) - throw new IllegalArgumentException("origin is null"); - if (target == null) - throw new IllegalArgumentException("target is null"); - Pattern at = active == null ? null : Pattern.compile(active); - Pattern lo = lock == null ? null : Pattern.compile(lock); - Mirror originMirror = Mirror.me(origin); - Mirror targetMirror = Mirror.me(target); - Field[] fields = targetMirror.getFields(); - for (Field field : originMirror.getFields()) { - String name = field.getName(); - if (at != null && !at.matcher(name).find()) - continue; - if (lo != null && lo.matcher(name).find()) - continue; - if (ignoreStatic && Modifier.isStatic(field.getModifiers())) - continue; - Object val = originMirror.getValue(origin, field); - if (ignoreNull && val == null) - continue; - for (Field _field : fields) { - if (_field.getName().equals(field.getName())) { - targetMirror.setValue(target, _field, val); - } - } - // TODO 支持getter/setter比对 - } - return target; - } - - public static StringBuilder execOutput(String cmd) throws IOException { - return execOutput(Strings.splitIgnoreBlank(cmd, " "), Encoding.CHARSET_UTF8); - } - - public static StringBuilder execOutput(String cmd, Charset charset) throws IOException { - return execOutput(Strings.splitIgnoreBlank(cmd, " "), charset); - } - - public static StringBuilder execOutput(String cmd[]) throws IOException { - return execOutput(cmd, Encoding.CHARSET_UTF8); - } - - public static StringBuilder execOutput(String[] cmd, Charset charset) throws IOException { - Process p = Runtime.getRuntime().exec(cmd); - p.getOutputStream().close(); - InputStreamReader r = new InputStreamReader(p.getInputStream(), charset); - StringBuilder sb = new StringBuilder(); - Streams.readAndClose(r, sb); - return sb; - } - - public static void exec(String cmd, StringBuilder out, StringBuilder err) throws IOException { - exec(Strings.splitIgnoreBlank(cmd, " "), Encoding.CHARSET_UTF8, out, err); - } - - public static void exec(String[] cmd, StringBuilder out, StringBuilder err) throws IOException { - exec(cmd, Encoding.CHARSET_UTF8, out, err); - } - - public static void exec(String[] cmd, Charset charset, StringBuilder out, StringBuilder err) - throws IOException { - Process p = Runtime.getRuntime().exec(cmd); - p.getOutputStream().close(); - InputStreamReader sOut = new InputStreamReader(p.getInputStream(), charset); - Streams.readAndClose(sOut, out); - - InputStreamReader sErr = new InputStreamReader(p.getErrorStream(), charset); - Streams.readAndClose(sErr, err); - } - - public static Class loadClassQuite(String className) { - try { - return loadClass(className); - } - catch (ClassNotFoundException e) { - return null; - } - } - - public static byte[] toBytes(Object obj) { - try { - ByteArrayOutputStream bao = new ByteArrayOutputStream(); - ObjectOutputStream oos = new ObjectOutputStream(bao); - oos.writeObject(obj); - return bao.toByteArray(); - } - catch (IOException e) { - return null; - } - } - - @SuppressWarnings("unchecked") - public static T fromBytes(byte[] buf, Class klass) { - try { - return (T) new ObjectInputStream(new ByteArrayInputStream(buf)).readObject(); - } - catch (ClassNotFoundException e) { - return null; - } - catch (IOException e) { - return null; - } - } - - public static class JdkTool { - public static String getVersionLong() { - Properties sys = System.getProperties(); - return sys.getProperty("java.version"); - } - public static int getMajorVersion() { - String ver = getVersionLong(); - if (Strings.isBlank(ver)) - return 6; - String[] tmp = ver.split("\\."); - if (tmp.length < 2) - return 6; - int t = Integer.parseInt(tmp[0]); - if (t > 1) - return t; - return Integer.parseInt(tmp[1]); - } - public static boolean isEarlyAccess() { - String ver = getVersionLong(); - if (Strings.isBlank(ver)) - return false; - return ver.contains("-ea"); - } - } -} +package org.nutz.lang; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.PrintStream; +import java.io.Reader; +import java.io.StringReader; +import java.io.Writer; +import java.lang.management.ManagementFactory; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.nio.charset.Charset; +import java.security.InvalidKeyException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Properties; +import java.util.Queue; +import java.util.Set; +import java.util.regex.Pattern; + +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import javax.servlet.http.HttpServletRequest; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.ParserConfigurationException; + +import org.nutz.castor.Castors; +import org.nutz.castor.FailToCastObjectException; +import org.nutz.dao.entity.annotation.Column; +import org.nutz.json.Json; +import org.nutz.lang.reflect.ReflectTool; +import org.nutz.lang.stream.StringInputStream; +import org.nutz.lang.stream.StringOutputStream; +import org.nutz.lang.stream.StringWriter; +import org.nutz.lang.util.Context; +import org.nutz.lang.util.NutMap; +import org.nutz.lang.util.NutType; +import org.nutz.lang.util.Regex; +import org.nutz.lang.util.SimpleContext; + +/** + * 这些帮助函数让 Java 的某些常用功能变得更简单 + * + * @author zozoh(zozohtnt@gmail.com) + * @author wendal(wendal1985@gmail.com) + * @author bonyfish(mc02cxj@gmail.com) + * @author wizzer(wizzer.cn@gmail.com) + */ +public abstract class Lang { + + public static int HASH_BUFF_SIZE = 16 * 1024; + + private static final Pattern IPV4_PATTERN = Pattern.compile("^(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}$"); + + private static final Pattern IPV6_STD_PATTERN = Pattern.compile("^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$"); + + private static final Pattern IPV6_HEX_COMPRESSED_PATTERN = Pattern.compile("^((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)$"); + + public static boolean isIPv4Address(final String input) { + return IPV4_PATTERN.matcher(input).matches(); + } + + public static boolean isIPv6StdAddress(final String input) { + return IPV6_STD_PATTERN.matcher(input).matches(); + } + + public static boolean isIPv6HexCompressedAddress(final String input) { + return IPV6_HEX_COMPRESSED_PATTERN.matcher(input).matches(); + } + + public static boolean isIPv6Address(final String input) { + return isIPv6StdAddress(input) || isIPv6HexCompressedAddress(input); + } + + public static ComboException comboThrow(Throwable... es) { + ComboException ce = new ComboException(); + for (Throwable e : es) + ce.add(e); + return ce; + } + + /** + * 生成一个未实现的运行时异常 + * + * @return 一个未实现的运行时异常 + */ + public static RuntimeException noImplement() { + return new RuntimeException("Not implement yet!"); + } + + /** + * 生成一个不可能的运行时异常 + * + * @return 一个不可能的运行时异常 + */ + public static RuntimeException impossible() { + return new RuntimeException("r u kidding me?! It is impossible!"); + } + + /** + * 根据格式化字符串,生成运行时异常 + * + * @param format + * 格式 + * @param args + * 参数 + * @return 运行时异常 + */ + public static RuntimeException makeThrow(String format, Object... args) { + return new RuntimeException(String.format(format, args)); + } + + /** + * 根据格式化字符串,生成一个指定的异常。 + * + * @param classOfT + * 异常类型, 需要有一个字符串为参数的构造函数 + * @param format + * 格式 + * @param args + * 参数 + * @return 异常对象 + */ + @SuppressWarnings("unchecked") + public static T makeThrow(Class classOfT, + String format, + Object... args) { + if (classOfT == RuntimeException.class) + return (T) new RuntimeException(String.format(format, args)); + return Mirror.me(classOfT).born(String.format(format, args)); + } + + /** + * 将抛出对象包裹成运行时异常,并增加自己的描述 + * + * @param e + * 抛出对象 + * @param fmt + * 格式 + * @param args + * 参数 + * @return 运行时异常 + */ + public static RuntimeException wrapThrow(Throwable e, String fmt, Object... args) { + return new RuntimeException(String.format(fmt, args), e); + } + + /** + * 用运行时异常包裹抛出对象,如果抛出对象本身就是运行时异常,则直接返回。 + *

+ * 如果是 InvocationTargetException,那么将其剥离,只包裹其 TargetException + * + * @param e + * 抛出对象 + * @return 运行时异常 + */ + public static RuntimeException wrapThrow(Throwable e) { + if (e instanceof RuntimeException) + return (RuntimeException) e; + if (e instanceof InvocationTargetException) + return wrapThrow(((InvocationTargetException) e).getTargetException()); + return new RuntimeException(e); + } + + /** + * 用一个指定可抛出类型来包裹一个抛出对象。这个指定的可抛出类型需要有一个构造函数 接受 Throwable 类型的对象 + * + * @param e + * 抛出对象 + * @param wrapper + * 包裹类型 + * @return 包裹后对象 + */ + @SuppressWarnings("unchecked") + public static T wrapThrow(Throwable e, Class wrapper) { + if (wrapper.isAssignableFrom(e.getClass())) + return (T) e; + return Mirror.me(wrapper).born(e); + } + + public static Throwable unwrapThrow(Throwable e) { + if (e == null) + return null; + if (e instanceof InvocationTargetException) { + InvocationTargetException itE = (InvocationTargetException) e; + if (itE.getTargetException() != null) + return unwrapThrow(itE.getTargetException()); + } + if (e instanceof RuntimeException && e.getCause() != null) + return unwrapThrow(e.getCause()); + return e; + } + + public static boolean isCauseBy(Throwable e, Class causeType) { + if (e.getClass() == causeType) + return true; + Throwable cause = e.getCause(); + if (null == cause) + return false; + return isCauseBy(cause, causeType); + } + + /** + * 判断两个对象是否相等。 这个函数用处是: + *

    + *
  • 可以容忍 null + *
  • 可以容忍不同类型的 Number + *
  • 对数组,集合, Map 会深层比较 + *
+ * 当然,如果你重写的 equals 方法会优先 + * + * @param a0 + * 比较对象1 + * @param a1 + * 比较对象2 + * @return 是否相等 + */ + public static boolean equals(Object a0, Object a1) { + if (a0 == a1) + return true; + + if (a0 == null && a1 == null) + return true; + + if (a0 == null || a1 == null) + return false; + + // 简单的判断是否等于 + if (a0.equals(a1)) + return true; + + Mirror mi = Mirror.me(a0); + + // 简单类型,变字符串比较,或者正则表达式 + if (mi.isSimple() || mi.is(Pattern.class)) { + return a0.toString().equals(a1.toString()); + } + + // 如果类型就不能互相转换,那么一定是错的 + if (!a0.getClass().isAssignableFrom(a1.getClass()) + && !a1.getClass().isAssignableFrom(a0.getClass())) + return false; + + // Map + if (a0 instanceof Map && a1 instanceof Map) { + Map m1 = (Map) a0; + Map m2 = (Map) a1; + if (m1.size() != m2.size()) + return false; + for (Entry e : m1.entrySet()) { + Object key = e.getKey(); + if (!m2.containsKey(key) || !equals(m1.get(key), m2.get(key))) + return false; + } + return true; + } + // 数组 + else if (a0.getClass().isArray() && a1.getClass().isArray()) { + int len = Array.getLength(a0); + if (len != Array.getLength(a1)) + return false; + for (int i = 0; i < len; i++) { + if (!equals(Array.get(a0, i), Array.get(a1, i))) + return false; + } + return true; + } + // 集合 + else if (a0 instanceof Collection && a1 instanceof Collection) { + Collection c0 = (Collection) a0; + Collection c1 = (Collection) a1; + if (c0.size() != c1.size()) + return false; + + Iterator it0 = c0.iterator(); + Iterator it1 = c1.iterator(); + + while (it0.hasNext()) { + Object o0 = it0.next(); + Object o1 = it1.next(); + if (!equals(o0, o1)) + return false; + } + + return true; + } + + // 一定不相等 + return false; + } + + /** + * 判断一个数组内是否包括某一个对象。 它的比较将通过 equals(Object,Object) 方法 + * + * @param array + * 数组 + * @param ele + * 对象 + * @return true 包含 false 不包含 + */ + public static boolean contains(T[] array, T ele) { + if (null == array) + return false; + for (T e : array) { + if (equals(e, ele)) + return true; + } + return false; + } + + /** + * 从一个文本输入流读取所有内容,并将该流关闭 + * + * @param reader + * 文本输入流 + * @return 输入流所有内容 + */ + public static String readAll(Reader reader) { + if (!(reader instanceof BufferedReader)) + reader = new BufferedReader(reader); + try { + StringBuilder sb = new StringBuilder(); + + char[] data = new char[64]; + int len; + while (true) { + if ((len = reader.read(data)) == -1) + break; + sb.append(data, 0, len); + } + return sb.toString(); + } + catch (IOException e) { + throw Lang.wrapThrow(e); + } + finally { + Streams.safeClose(reader); + } + } + + /** + * 将一段字符串写入一个文本输出流,并将该流关闭 + * + * @param writer + * 文本输出流 + * @param str + * 字符串 + */ + public static void writeAll(Writer writer, String str) { + try { + writer.write(str); + writer.flush(); + } + catch (IOException e) { + throw Lang.wrapThrow(e); + } + finally { + Streams.safeClose(writer); + } + } + + /** + * 根据一段文本模拟出一个输入流对象 + * + * @param cs + * 文本 + * @return 输出流对象 + */ + public static InputStream ins(CharSequence cs) { + return new StringInputStream(cs); + } + + /** + * 根据一段文本模拟出一个文本输入流对象 + * + * @param cs + * 文本 + * @return 文本输出流对象 + */ + public static Reader inr(CharSequence cs) { + return new StringReader(cs.toString()); + } + + /** + * 根据一个 StringBuilder 模拟一个文本输出流对象 + * + * @param sb + * StringBuilder 对象 + * @return 文本输出流对象 + */ + public static Writer opw(StringBuilder sb) { + return new StringWriter(sb); + } + + /** + * 根据一个 StringBuilder 模拟一个输出流对象 + * + * @param sb + * StringBuilder 对象 + * @return 输出流对象 + */ + public static StringOutputStream ops(StringBuilder sb) { + return new StringOutputStream(sb); + } + + /** + * 较方便的创建一个数组,比如: + * + *
+     * String[] strs = Lang.array("A", "B", "A"); => ["A","B","A"]
+     * 
+ * + * @param eles + * 可变参数 + * @return 数组对象 + */ + public static T[] array(T... eles) { + return eles; + } + + /** + * 较方便的创建一个没有重复的数组,比如: + * + *
+     * String[] strs = Lang.arrayUniq("A","B","A");  => ["A","B"]
+     * String[] strs = Lang.arrayUniq();  => null
+     * 
+ * + * 返回的顺序会遵循输入的顺序 + * + * @param eles + * 可变参数 + * @return 数组对象 + */ + @SuppressWarnings("unchecked") + public static T[] arrayUniq(T... eles) { + if (null == eles || eles.length == 0) + return null; + // 记录重复 + HashSet set = new HashSet(eles.length); + for (T ele : eles) { + set.add(ele); + } + // 循环 + T[] arr = (T[]) Array.newInstance(eles[0].getClass(), set.size()); + int index = 0; + for (T ele : eles) { + if (set.remove(ele)) + Array.set(arr, index++, ele); + } + return arr; + + } + + /** + * 判断一个对象是否为空。它支持如下对象类型: + *
    + *
  • null : 一定为空 + *
  • 数组 + *
  • 集合 + *
  • Map + *
  • 其他对象 : 一定不为空 + *
+ * + * @param obj + * 任意对象 + * @return 是否为空 + */ + public static boolean isEmpty(Object obj) { + if (obj == null) + return true; + if (obj.getClass().isArray()) + return Array.getLength(obj) == 0; + if (obj instanceof Collection) + return ((Collection) obj).isEmpty(); + if (obj instanceof Map) + return ((Map) obj).isEmpty(); + return false; + } + + /** + * 判断一个数组是否是空数组 + * + * @param ary + * 数组 + * @return null 或者空数组都为 true 否则为 false + */ + public static boolean isEmptyArray(T[] ary) { + return null == ary || ary.length == 0; + } + + /** + * 较方便的创建一个列表,比如: + * + *
+     * List<Pet> pets = Lang.list(pet1, pet2, pet3);
+     * 
+ * + * 注,这里的 List,是 ArrayList 的实例 + * + * @param eles + * 可变参数 + * @return 列表对象 + */ + public static ArrayList list(T... eles) { + ArrayList list = new ArrayList(eles.length); + for (T ele : eles) + list.add(ele); + return list; + } + + /** + * 创建一个 Hash 集合 + * + * @param eles + * 可变参数 + * @return 集合对象 + */ + public static Set set(T... eles) { + Set set = new HashSet(); + for (T ele : eles) + set.add(ele); + return set; + } + + /** + * 将多个数组,合并成一个数组。如果这些数组为空,则返回 null + * + * @param arys + * 数组对象 + * @return 合并后的数组对象 + */ + @SuppressWarnings("unchecked") + public static T[] merge(T[]... arys) { + Queue list = new LinkedList(); + for (T[] ary : arys) + if (null != ary) + for (T e : ary) + if (null != e) + list.add(e); + if (list.isEmpty()) + return null; + Class type = (Class) list.peek().getClass(); + return list.toArray((T[]) Array.newInstance(type, list.size())); + } + + /** + * 将一个对象添加成为一个数组的第一个元素,从而生成一个新的数组 + * + * @param e + * 对象 + * @param eles + * 数组 + * @return 新数组 + */ + @SuppressWarnings("unchecked") + public static T[] arrayFirst(T e, T[] eles) { + try { + if (null == eles || eles.length == 0) { + T[] arr = (T[]) Array.newInstance(e.getClass(), 1); + arr[0] = e; + return arr; + } + T[] arr = (T[]) Array.newInstance(eles.getClass().getComponentType(), eles.length + 1); + arr[0] = e; + for (int i = 0; i < eles.length; i++) { + arr[i + 1] = eles[i]; + } + return arr; + } + catch (NegativeArraySizeException e1) { + throw Lang.wrapThrow(e1); + } + } + + /** + * 将一个对象添加成为一个数组的最后一个元素,从而生成一个新的数组 + * + * @param e + * 对象 + * @param eles + * 数组 + * @return 新数组 + */ + @SuppressWarnings("unchecked") + public static T[] arrayLast(T[] eles, T e) { + try { + if (null == eles || eles.length == 0) { + T[] arr = (T[]) Array.newInstance(e.getClass(), 1); + arr[0] = e; + return arr; + } + T[] arr = (T[]) Array.newInstance(eles.getClass().getComponentType(), eles.length + 1); + for (int i = 0; i < eles.length; i++) { + arr[i] = eles[i]; + } + arr[eles.length] = e; + return arr; + } + catch (NegativeArraySizeException e1) { + throw Lang.wrapThrow(e1); + } + } + + /** + * 将一个数组转换成字符串 + *

+ * 所有的元素都被格式化字符串包裹。 这个格式话字符串只能有一个占位符, %s, %d 等,均可,请视你的数组内容而定 + * + * @param fmt + * 格式 + * @param objs + * 数组 + * @return 拼合后的字符串 + */ + public static StringBuilder concatBy(String fmt, T[] objs) { + StringBuilder sb = new StringBuilder(); + for (T obj : objs) + sb.append(String.format(fmt, obj)); + return sb; + } + + /** + * 将一个数组转换成字符串 + *

+ * 所有的元素都被格式化字符串包裹。 这个格式话字符串只能有一个占位符, %s, %d 等,均可,请视你的数组内容而定 + *

+ * 每个元素之间,都会用一个给定的字符分隔 + * + * @param ptn + * 格式 + * @param c + * 分隔符 + * @param objs + * 数组 + * @return 拼合后的字符串 + */ + public static StringBuilder concatBy(String ptn, Object c, T[] objs) { + StringBuilder sb = new StringBuilder(); + for (T obj : objs) + sb.append(String.format(ptn, obj)).append(c); + if (sb.length() > 0) + sb.deleteCharAt(sb.length() - 1); + return sb; + } + + /** + * 将一个数组转换成字符串 + *

+ * 每个元素之间,都会用一个给定的字符分隔 + * + * @param c + * 分隔符 + * @param objs + * 数组 + * @return 拼合后的字符串 + */ + public static StringBuilder concat(Object c, T[] objs) { + StringBuilder sb = new StringBuilder(); + if (null == objs || 0 == objs.length) + return sb; + + sb.append(objs[0]); + for (int i = 1; i < objs.length; i++) + sb.append(c).append(objs[i]); + + return sb; + } + + /** + * 清除数组中的特定值 + * + * @param objs + * 数组 + * @param val + * 值,可以是 null,如果是对象,则会用 equals 来比较 + * @return 新的数组实例 + */ + @SuppressWarnings("unchecked") + public static T[] without(T[] objs, T val) { + if (null == objs || objs.length == 0) { + return objs; + } + List list = new ArrayList(objs.length); + Class eleType = null; + for (T obj : objs) { + if (obj == val || (null != obj && null != val && obj.equals(val))) + continue; + if (null == eleType && obj != null) + eleType = obj.getClass(); + list.add(obj); + } + if (list.isEmpty()) { + return (T[]) new Object[0]; + } + return list.toArray((T[]) Array.newInstance(eleType, list.size())); + } + + /** + * 将一个长整型数组转换成字符串 + *

+ * 每个元素之间,都会用一个给定的字符分隔 + * + * @param c + * 分隔符 + * @param vals + * 数组 + * @return 拼合后的字符串 + */ + public static StringBuilder concat(Object c, long[] vals) { + StringBuilder sb = new StringBuilder(); + if (null == vals || 0 == vals.length) + return sb; + + sb.append(vals[0]); + for (int i = 1; i < vals.length; i++) + sb.append(c).append(vals[i]); + + return sb; + } + + /** + * 将一个整型数组转换成字符串 + *

+ * 每个元素之间,都会用一个给定的字符分隔 + * + * @param c + * 分隔符 + * @param vals + * 数组 + * @return 拼合后的字符串 + */ + public static StringBuilder concat(Object c, int[] vals) { + StringBuilder sb = new StringBuilder(); + if (null == vals || 0 == vals.length) + return sb; + + sb.append(vals[0]); + for (int i = 1; i < vals.length; i++) + sb.append(c).append(vals[i]); + + return sb; + } + + /** + * 将一个数组的部分元素转换成字符串 + *

+ * 每个元素之间,都会用一个给定的字符分隔 + * + * @param offset + * 开始元素的下标 + * @param len + * 元素数量 + * @param c + * 分隔符 + * @param objs + * 数组 + * @return 拼合后的字符串 + */ + public static StringBuilder concat(int offset, int len, Object c, T[] objs) { + StringBuilder sb = new StringBuilder(); + if (null == objs || len < 0 || 0 == objs.length) + return sb; + + if (offset < objs.length) { + sb.append(objs[offset]); + for (int i = 1; i < len && i + offset < objs.length; i++) { + sb.append(c).append(objs[i + offset]); + } + } + return sb; + } + + /** + * 将一个数组所有元素拼合成一个字符串 + * + * @param objs + * 数组 + * @return 拼合后的字符串 + */ + public static StringBuilder concat(T[] objs) { + StringBuilder sb = new StringBuilder(); + for (T e : objs) + sb.append(e.toString()); + return sb; + } + + /** + * 将一个数组部分元素拼合成一个字符串 + * + * @param offset + * 开始元素的下标 + * @param len + * 元素数量 + * @param array + * 数组 + * @return 拼合后的字符串 + */ + public static StringBuilder concat(int offset, int len, T[] array) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < len; i++) { + sb.append(array[i + offset].toString()); + } + return sb; + } + + /** + * 将一个集合转换成字符串 + *

+ * 每个元素之间,都会用一个给定的字符分隔 + * + * @param c + * 分隔符 + * @param coll + * 集合 + * @return 拼合后的字符串 + */ + public static StringBuilder concat(Object c, Collection coll) { + StringBuilder sb = new StringBuilder(); + if (null == coll || coll.isEmpty()) + return sb; + return concat(c, coll.iterator()); + } + + /** + * 将一个迭代器转换成字符串 + *

+ * 每个元素之间,都会用一个给定的字符分隔 + * + * @param c + * 分隔符 + * @param it + * 集合 + * @return 拼合后的字符串 + */ + public static StringBuilder concat(Object c, Iterator it) { + StringBuilder sb = new StringBuilder(); + if (it == null || !it.hasNext()) + return sb; + sb.append(it.next()); + while (it.hasNext()) + sb.append(c).append(it.next()); + return sb; + } + + /** + * 将一个或者多个数组填入一个集合。 + * + * @param + * 集合类型 + * @param + * 数组元素类型 + * @param coll + * 集合 + * @param objss + * 数组 (数目可变) + * @return 集合对象 + */ + public static , T> C fill(C coll, T[]... objss) { + for (T[] objs : objss) + for (T obj : objs) + coll.add(obj); + return coll; + } + + /** + * 将一个集合变成 Map。 + * + * @param mapClass + * Map 的类型 + * @param coll + * 集合对象 + * @param keyFieldName + * 采用集合中元素的哪个一个字段为键。 + * @return Map 对象 + */ + public static > T collection2map(Class mapClass, + Collection coll, + String keyFieldName) { + if (null == coll) + return null; + T map = createMap(mapClass); + if (coll.size() > 0) { + Iterator it = coll.iterator(); + Object obj = it.next(); + Mirror mirror = Mirror.me(obj.getClass()); + Object key = mirror.getValue(obj, keyFieldName); + map.put(key, obj); + for (; it.hasNext();) { + obj = it.next(); + key = mirror.getValue(obj, keyFieldName); + map.put(key, obj); + } + } + return (T) map; + } + + /** + * 将集合变成 ArrayList + * + * @param col + * 集合对象 + * @return 列表对象 + */ + @SuppressWarnings("unchecked") + public static List collection2list(Collection col) { + if (null == col) + return null; + if (col.size() == 0) + return new ArrayList(0); + Class eleType = (Class) col.iterator().next().getClass(); + return collection2list(col, eleType); + } + + /** + * 将集合编程变成指定类型的列表 + * + * @param col + * 集合对象 + * @param eleType + * 列表类型 + * @return 列表对象 + */ + public static List collection2list(Collection col, Class eleType) { + if (null == col) + return null; + List list = new ArrayList(col.size()); + for (Object obj : col) + list.add(Castors.me().castTo(obj, eleType)); + return list; + } + + /** + * 将集合变成数组,数组的类型为集合的第一个元素的类型。如果集合为空,则返回 null + * + * @param coll + * 集合对象 + * @return 数组 + */ + @SuppressWarnings("unchecked") + public static E[] collection2array(Collection coll) { + if (null == coll) + return null; + if (coll.size() == 0) + return (E[]) new Object[0]; + + Class eleType = (Class) Lang.first(coll).getClass(); + return collection2array(coll, eleType); + } + + /** + * 将集合变成指定类型的数组 + * + * @param col + * 集合对象 + * @param eleType + * 数组元素类型 + * @return 数组 + */ + @SuppressWarnings("unchecked") + public static E[] collection2array(Collection col, Class eleType) { + if (null == col) + return null; + Object re = Array.newInstance(eleType, col.size()); + int i = 0; + for (Iterator it = col.iterator(); it.hasNext();) { + Object obj = it.next(); + if (null == obj) + Array.set(re, i++, null); + else + Array.set(re, i++, Castors.me().castTo(obj, eleType)); + } + return (E[]) re; + } + + /** + * 将一个数组变成 Map + * + * @param mapClass + * Map 的类型 + * @param array + * 数组 + * @param keyFieldName + * 采用集合中元素的哪个一个字段为键。 + * @return Map 对象 + */ + public static > T array2map(Class mapClass, + Object array, + String keyFieldName) { + if (null == array) + return null; + T map = createMap(mapClass); + int len = Array.getLength(array); + if (len > 0) { + Object obj = Array.get(array, 0); + Mirror mirror = Mirror.me(obj.getClass()); + for (int i = 0; i < len; i++) { + obj = Array.get(array, i); + Object key = mirror.getValue(obj, keyFieldName); + map.put(key, obj); + } + } + return map; + } + + @SuppressWarnings("unchecked") + private static > T createMap(Class mapClass) { + T map; + try { + map = mapClass.newInstance(); + } + catch (Exception e) { + map = (T) new HashMap(); + } + if (!mapClass.isAssignableFrom(map.getClass())) { + throw Lang.makeThrow("Fail to create map [%s]", mapClass.getName()); + } + return map; + } + + /** + * 将数组转换成一个列表。 + * + * @param array + * 原始数组 + * @return 新列表 + * + * @see org.nutz.castor.Castors + */ + public static List array2list(T[] array) { + if (null == array) + return null; + List re = new ArrayList(array.length); + for (T obj : array) + re.add(obj); + return re; + } + + /** + * 将数组转换成一个列表。将会采用 Castor 来深层转换数组元素 + * + * @param array + * 原始数组 + * @param eleType + * 新列表的元素类型 + * @return 新列表 + * + * @see org.nutz.castor.Castors + */ + public static List array2list(Object array, Class eleType) { + if (null == array) + return null; + int len = Array.getLength(array); + List re = new ArrayList(len); + for (int i = 0; i < len; i++) { + Object obj = Array.get(array, i); + re.add(Castors.me().castTo(obj, eleType)); + } + return re; + } + + /** + * 将数组转换成另外一种类型的数组。将会采用 Castor 来深层转换数组元素 + * + * @param array + * 原始数组 + * @param eleType + * 新数组的元素类型 + * @return 新数组 + * @throws FailToCastObjectException + * + * @see org.nutz.castor.Castors + */ + public static Object array2array(Object array, Class eleType) + throws FailToCastObjectException { + if (null == array) + return null; + int len = Array.getLength(array); + Object re = Array.newInstance(eleType, len); + for (int i = 0; i < len; i++) { + Array.set(re, i, Castors.me().castTo(Array.get(array, i), eleType)); + } + return re; + } + + /** + * 将数组转换成Object[] 数组。将会采用 Castor 来深层转换数组元素 + * + * @param args + * 原始数组 + * @param pts + * 新数组的元素类型 + * @return 新数组 + * @throws FailToCastObjectException + * + * @see org.nutz.castor.Castors + */ + public static Object[] array2ObjectArray(T[] args, Class[] pts) + throws FailToCastObjectException { + if (null == args) + return null; + Object[] newArgs = new Object[args.length]; + for (int i = 0; i < args.length; i++) { + newArgs[i] = Castors.me().castTo(args[i], pts[i]); + } + return newArgs; + } + + /** + * 根据一个 Map,和给定的对象类型,创建一个新的 JAVA 对象 + * + * @param src + * Map 对象 + * @param toType + * JAVA 对象类型 + * @return JAVA 对象 + * @throws FailToCastObjectException + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public static T map2Object(Map src, Class toType) + throws FailToCastObjectException { + if (null == toType) + throw new FailToCastObjectException("target type is Null"); + // 类型相同 + if (toType == Map.class) + return (T) src; + // 也是一种 Map + if (Map.class.isAssignableFrom(toType)) { + Map map; + try { + map = (Map) toType.newInstance(); + map.putAll(src); + return (T) map; + } + catch (Exception e) { + throw new FailToCastObjectException("target type fail to born!", unwrapThrow(e)); + } + + } + // 数组 + if (toType.isArray()) + return (T) Lang.collection2array(src.values(), toType.getComponentType()); + // List + if (List.class == toType) { + return (T) Lang.collection2list(src.values()); + } + + // POJO + Mirror mirror = Mirror.me(toType); + T obj = mirror.born(); + for (Field field : mirror.getFields()) { + Object v = null; + if (!Lang.isAndroid && field.isAnnotationPresent(Column.class)) { + String cv = field.getAnnotation(Column.class).value(); + v = src.get(cv); + } + + if (null == v && src.containsKey(field.getName())) { + v = src.get(field.getName()); + } + + if (null != v) { + // Class ft = field.getType(); + // 获取泛型基类中的字段真实类型, https://github.com/nutzam/nutz/issues/1288 + Class ft = ReflectTool.getGenericFieldType(toType, field); + Object vv = null; + // 集合 + if (v instanceof Collection + && (ft.isArray() || Collection.class.isAssignableFrom(ft))) { + Collection c = (Collection) v; + // 集合到数组 + if (ft.isArray()) { + vv = Lang.collection2array(c, ft.getComponentType()); + } + // 集合到集合 + else { + // 创建 + Collection newCol; + // Class eleType = Mirror.getGenericTypes(field, 0); + Class eleType = ReflectTool.getParameterRealGenericClass(toType, + field.getGenericType(), + 0); + if (ft == List.class) { + newCol = new ArrayList(c.size()); + } else if (ft == Set.class) { + newCol = new LinkedHashSet(); + } else { + try { + newCol = (Collection) ft.newInstance(); + } + catch (Exception e) { + throw Lang.wrapThrow(e); + } + } + // 赋值 + for (Object ele : c) { + newCol.add(Castors.me().castTo(ele, eleType)); + } + vv = newCol; + } + } + // Map + else if (v instanceof Map && Map.class.isAssignableFrom(ft)) { + // 创建 + final Map map; + // Map 接口 + if (ft == Map.class) { + map = new HashMap(); + } + // 自己特殊的 Map + else { + try { + map = (Map) ft.newInstance(); + } + catch (Exception e) { + throw new FailToCastObjectException("target type fail to born!", e); + } + } + // 赋值 + // final Class valType = Mirror.getGenericTypes(field, + // 1); + // map的key和value字段类型 + final Class keyType = ReflectTool.getParameterRealGenericClass(toType, + field.getGenericType(), + 0); + final Class valType = ReflectTool.getParameterRealGenericClass(toType, + field.getGenericType(), + 1); + each(v, new Each() { + public void invoke(int i, Entry en, int length) { + map.put(Castors.me().castTo(en.getKey(), keyType), + Castors.me().castTo(en.getValue(), valType)); + } + }); + vv = map; + } + // 强制转换 + else { + vv = Castors.me().castTo(v, ft); + } + mirror.setValue(obj, field, vv); + } + } + return obj; + } + + /** + * 根据一段字符串,生成一个 Map 对象。 + * + * @param str + * 参照 JSON 标准的字符串,但是可以没有前后的大括号 + * @return Map 对象 + */ + public static NutMap map(String str) { + if (null == str) + return null; + str = Strings.trim(str); + if (!Strings.isEmpty(str) + && (Strings.isQuoteBy(str, '{', '}') || Strings.isQuoteBy(str, '(', ')'))) { + return Json.fromJson(NutMap.class, str); + } + return Json.fromJson(NutMap.class, "{" + str + "}"); + } + + /** + * 将一个 Map 所有的键都按照回调进行修改 + * + * 本函数遇到数组或者集合,会自动处理每个元素 + * + * @param obj + * 要转换的 Map 或者 集合或者数组 + * + * @param mkc + * 键值修改的回调 + * @param recur + * 遇到 Map 是否递归 + * + * @see MapKeyConvertor + */ + @SuppressWarnings("unchecked") + public static void convertMapKey(Object obj, MapKeyConvertor mkc, boolean recur) { + // Map + if (obj instanceof Map) { + Map map = (Map) obj; + NutMap map2 = new NutMap(); + for (Map.Entry en : map.entrySet()) { + String key = en.getKey(); + Object val = en.getValue(); + + if (recur) + convertMapKey(val, mkc, recur); + + String newKey = mkc.convertKey(key); + map2.put(newKey, val); + } + map.clear(); + map.putAll(map2); + } + // Collection + else if (obj instanceof Collection) { + for (Object ele : (Collection) obj) { + convertMapKey(ele, mkc, recur); + } + } + // Array + else if (obj.getClass().isArray()) { + for (Object ele : (Object[]) obj) { + convertMapKey(ele, mkc, recur); + } + } + } + + /** + * 创建一个一个键的 Map 对象 + * + * @param key + * 键 + * @param v + * 值 + * @return Map 对象 + */ + public static NutMap map(String key, Object v) { + return new NutMap().addv(key, v); + } + + /** + * 根据一个格式化字符串,生成 Map 对象 + * + * @param fmt + * 格式化字符串 + * @param args + * 字符串参数 + * @return Map 对象 + */ + public static NutMap mapf(String fmt, Object... args) { + return map(String.format(fmt, args)); + } + + /** + * 创建一个新的上下文对象 + * + * @return 一个新创建的上下文对象 + */ + public static Context context() { + return new SimpleContext(); + } + + /** + * 根据key,val创建一个新的上下文对象 + * + * @param key + * @param val + * @return + */ + public static Context context(String key, Object val) { + return context().set(key, val); + } + + /** + * 根据一个 Map 包裹成一个上下文对象 + * + * @param map + * Map 对象 + * + * @return 一个新创建的上下文对象 + */ + public static Context context(Map map) { + return new SimpleContext(map); + } + + /** + * 根据一段 JSON 字符串,生产一个新的上下文对象 + * + * @param fmt + * JSON 字符串模板 + * @param args + * 模板参数 + * + * @return 一个新创建的上下文对象 + */ + public static Context contextf(String fmt, Object... args) { + return context(Lang.mapf(fmt, args)); + } + + /** + * 根据一段 JSON 字符串,生产一个新的上下文对象 + * + * @return 一个新创建的上下文对象 + */ + public static Context context(String str) { + return context(map(str)); + } + + /** + * 根据一段字符串,生成一个List 对象。 + * + * @param str + * 参照 JSON 标准的字符串,但是可以没有前后的中括号 + * @return List 对象 + */ + @SuppressWarnings("unchecked") + public static List list4(String str) { + if (null == str) + return null; + if ((str.length() > 0 && str.charAt(0) == '[') && str.endsWith("]")) + return (List) Json.fromJson(str); + return (List) Json.fromJson("[" + str + "]"); + } + + /** + * 获得一个对象的长度。它可以接受: + *
    + *
  • null : 0 + *
  • 数组 + *
  • 集合 + *
  • Map + *
  • 一般 Java 对象。 返回 1 + *
+ * 如果你想让你的 Java 对象返回不是 1 , 请在对象中声明 length() 函数 + * + * @param obj + * @return 对象长度 + * @deprecated 这玩意很脑残,为啥最后要动态调个 "length",导致字符串类很麻烦,以后用 Lang.eleSize 函数代替吧 + */ + @Deprecated + public static int length(Object obj) { + if (null == obj) + return 0; + if (obj.getClass().isArray()) { + return Array.getLength(obj); + } else if (obj instanceof Collection) { + return ((Collection) obj).size(); + } else if (obj instanceof Map) { + return ((Map) obj).size(); + } + try { + return (Integer) Mirror.me(obj.getClass()).invoke(obj, "length"); + } + catch (Exception e) {} + return 1; + } + + /** + * 获得一个容器(Map/集合/数组)对象包含的元素数量 + *
    + *
  • null : 0 + *
  • 数组 + *
  • 集合 + *
  • Map + *
  • 一般 Java 对象。 返回 1 + *
+ * + * @param obj + * @return 对象长度 + * @since Nutz 1.r.62 + */ + public static int eleSize(Object obj) { + // 空指针,就是 0 + if (null == obj) + return 0; + // 数组 + if (obj.getClass().isArray()) { + return Array.getLength(obj); + } + // 容器 + if (obj instanceof Collection) { + return ((Collection) obj).size(); + } + // Map + if (obj instanceof Map) { + return ((Map) obj).size(); + } + // 其他的就是 1 咯 + return 1; + } + + /** + * 如果是数组或集合取得第一个对象。 否则返回自身 + * + * @param obj + * 任意对象 + * @return 第一个代表对象 + */ + public static Object first(Object obj) { + if (null == obj) + return obj; + + if (obj instanceof Collection) { + Iterator it = ((Collection) obj).iterator(); + return it.hasNext() ? it.next() : null; + } + + if (obj.getClass().isArray()) + return Array.getLength(obj) > 0 ? Array.get(obj, 0) : null; + + return obj; + } + + /** + * 获取集合中的第一个元素,如果集合为空,返回 null + * + * @param coll + * 集合 + * @return 第一个元素 + */ + public static T first(Collection coll) { + if (null == coll || coll.isEmpty()) + return null; + return coll.iterator().next(); + } + + /** + * 获得表中的第一个名值对 + * + * @param map + * 表 + * @return 第一个名值对 + */ + public static Entry first(Map map) { + if (null == map || map.isEmpty()) + return null; + return map.entrySet().iterator().next(); + } + + /** + * 打断 each 循环 + */ + public static void Break() throws ExitLoop { + throw new ExitLoop(); + } + + /** + * 继续 each 循环,如果再递归,则停止递归 + */ + public static void Continue() throws ContinueLoop { + throw new ContinueLoop(); + } + + /** + * 用回调的方式,遍历一个对象,可以支持遍历 + *
    + *
  • 数组 + *
  • 集合 + *
  • Map + *
  • 单一元素 + *
+ * + * @param obj + * 对象 + * @param callback + * 回调 + */ + public static void each(Object obj, Each callback) { + each(obj, true, callback); + } + + /** + * 用回调的方式,遍历一个对象,可以支持遍历 + *
    + *
  • 数组 + *
  • 集合 + *
  • Map + *
  • 单一元素 + *
+ * + * @param obj + * 对象 + * @param loopMap + * 是否循环 Map,如果循环 Map 则主要看 callback 的 T,如果是 Map.Entry 则循环 Entry + * 否循环 value。如果本值为 false, 则将 Map 当作一个完整的对象来看待 + * @param callback + * 回调 + */ + @SuppressWarnings({"rawtypes", "unchecked"}) + public static void each(Object obj, boolean loopMap, Each callback) { + if (null == obj || null == callback) + return; + try { + // 循环开始 + if (callback instanceof Loop) + if (!((Loop) callback).begin()) + return; + + // 进行循环 + if (obj.getClass().isArray()) { + int len = Array.getLength(obj); + for (int i = 0; i < len; i++) + try { + callback.invoke(i, (T) Array.get(obj, i), len); + } + catch (ContinueLoop e) {} + catch (ExitLoop e) { + break; + } + } else if (obj instanceof Collection) { + int len = ((Collection) obj).size(); + int i = 0; + for (Iterator it = ((Collection) obj).iterator(); it.hasNext();) + try { + callback.invoke(i++, it.next(), len); + } + catch (ContinueLoop e) {} + catch (ExitLoop e) { + break; + } + } else if (loopMap && obj instanceof Map) { + Map map = (Map) obj; + int len = map.size(); + int i = 0; + Class eType = Mirror.getTypeParam(callback.getClass(), 0); + if (null != eType && eType != Object.class && eType.isAssignableFrom(Entry.class)) { + for (Object v : map.entrySet()) + try { + callback.invoke(i++, (T) v, len); + } + catch (ContinueLoop e) {} + catch (ExitLoop e) { + break; + } + + } else { + for (Object v : map.entrySet()) + try { + callback.invoke(i++, (T) ((Entry) v).getValue(), len); + } + catch (ContinueLoop e) {} + catch (ExitLoop e) { + break; + } + } + } else if (obj instanceof Iterator) { + Iterator it = (Iterator) obj; + int i = 0; + while (it.hasNext()) { + try { + callback.invoke(i++, (T) it.next(), -1); + } + catch (ContinueLoop e) {} + catch (ExitLoop e) { + break; + } + } + } else + try { + callback.invoke(0, (T) obj, 1); + } + catch (ContinueLoop e) {} + catch (ExitLoop e) {} + + // 循环结束 + if (callback instanceof Loop) + ((Loop) callback).end(); + } + catch (LoopException e) { + throw Lang.wrapThrow(e.getCause()); + } + } + + /** + * 安全的从一个数组获取一个元素,容忍 null 数组,以及支持负数的 index + *

+ * 如果该下标越界,则返回 null + * + * @param + * @param array + * 数组,如果为 null 则直接返回 null + * @param index + * 下标,-1 表示倒数第一个, -2 表示倒数第二个,以此类推 + * @return 数组元素 + */ + public static T get(T[] array, int index) { + if (null == array) + return null; + int i = index < 0 ? array.length + index : index; + if (i < 0 || i >= array.length) + return null; + return array[i]; + } + + /** + * 将一个抛出对象的异常堆栈,显示成一个字符串 + * + * @param e + * 抛出对象 + * @return 异常堆栈文本 + */ + public static String getStackTrace(Throwable e) { + StringBuilder sb = new StringBuilder(); + StringOutputStream sbo = new StringOutputStream(sb); + PrintStream ps = new PrintStream(sbo); + e.printStackTrace(ps); + ps.flush(); + return sbo.getStringBuilder().toString(); + } + + /** + * 将字符串解析成 boolean 值,支持更多的字符串 + *

    + *
  • 1 | 0 + *
  • yes | no + *
  • on | off + *
  • true | false + *
+ * + * @param s + * 字符串 + * @return 布尔值 + */ + public static boolean parseBoolean(String s) { + if (null == s || s.length() == 0) + return false; + if (s.length() > 5) + return true; + if ("0".equals(s)) + return false; + s = s.toLowerCase(); + return !"false".equals(s) && !"off".equals(s) && !"no".equals(s); + } + + /** + * 帮你快速获得一个 DocumentBuilder,方便 XML 解析。 + * + * @return 一个 DocumentBuilder 对象 + * @throws ParserConfigurationException + */ + public static DocumentBuilder xmls() throws ParserConfigurationException { + return Xmls.xmls(); + } + + /** + * 对Thread.sleep(long)的简单封装,不抛出任何异常 + * + * @param millisecond + * 休眠时间 + */ + public static void quiteSleep(long millisecond) { + try { + if (millisecond > 0) + Thread.sleep(millisecond); + } + catch (Throwable e) {} + } + + /** + * 将字符串,变成数字对象,现支持的格式为: + *
    + *
  • null - 整数 0
  • + *
  • 23.78 - 浮点 Float
  • + *
  • 0x45 - 16进制整数 Integer
  • + *
  • 78L - 长整数 Long
  • + *
  • 69 - 普通整数 Integer
  • + *
+ * + * @param s + * 参数 + * @return 数字对象 + */ + public static Number str2number(String s) { + // null 值 + if (null == s) { + return 0; + } + s = s.toUpperCase(); + // 浮点 + if (s.indexOf('.') != -1) { + char c = s.charAt(s.length() - 1); + if (c == 'F' || c == 'f') { + return Float.valueOf(s); + } + return Double.valueOf(s); + } + // 16进制整数 + if (s.startsWith("0X")) { + return Integer.valueOf(s.substring(2), 16); + } + // 长整数 + if (s.charAt(s.length() - 1) == 'L' || s.charAt(s.length() - 1) == 'l') { + return Long.valueOf(s.substring(0, s.length() - 1)); + } + // 普通整数 + Long re = Long.parseLong(s); + if (Integer.MAX_VALUE >= re && re >= Integer.MIN_VALUE) + return re.intValue(); + return re; + } + + @SuppressWarnings("unchecked") + private static > void obj2map(Object obj, + T map, + final Map memo) { + // 已经转换过了,不要递归转换 + if (null == obj || memo.containsKey(obj)) + return; + memo.put(obj, ""); + + // Fix issue #497 + // 如果是 Map,就直接 putAll 一下咯 + if (obj instanceof Map) { + map.putAll(__change_map_to_nutmap((Map) obj, memo)); + return; + } + + // 下面是普通的 POJO + Mirror mirror = Mirror.me(obj.getClass()); + Field[] flds = mirror.getFields(); + for (Field fld : flds) { + Object v = mirror.getValue(obj, fld); + if (null == v) { + continue; + } + Mirror mr = Mirror.me(v); + // 普通值 + if (mr.isSimple()) { + map.put(fld.getName(), v); + } + // 已经输出过了 + else if (memo.containsKey(v)) { + map.put(fld.getName(), null); + } + // 数组或者集合 + else if (mr.isColl()) { + final List list = new ArrayList(Lang.length(v)); + Lang.each(v, new Each() { + public void invoke(int index, Object ele, int length) { + __join_ele_to_list_as_map(list, ele, memo); + } + }); + map.put(fld.getName(), list); + } + // Map + else if (mr.isMap()) { + NutMap map2 = __change_map_to_nutmap((Map) v, memo); + map.put(fld.getName(), map2); + } + // 看来要递归 + else { + T sub; + try { + sub = (T) map.getClass().newInstance(); + } + catch (Exception e) { + throw Lang.wrapThrow(e); + } + obj2map(v, sub, memo); + map.put(fld.getName(), sub); + } + } + } + + @SuppressWarnings("unchecked") + private static NutMap __change_map_to_nutmap(Map map, + final Map memo) { + NutMap re = new NutMap(); + for (Map.Entry en : map.entrySet()) { + Object v = en.getValue(); + if (null == v) + continue; + Mirror mr = Mirror.me(v); + // 普通值 + if (mr.isSimple()) { + re.put(en.getKey(), v); + } + // 已经输出过了 + else if (memo.containsKey(v)) { + continue; + } + // 数组或者集合 + else if (mr.isColl()) { + final List list2 = new ArrayList(Lang.length(v)); + Lang.each(v, new Each() { + public void invoke(int index, Object ele, int length) { + __join_ele_to_list_as_map(list2, ele, memo); + } + }); + re.put(en.getKey(), list2); + } + // Map + else if (mr.isMap()) { + NutMap map2 = __change_map_to_nutmap((Map) v, memo); + re.put(en.getKey(), map2); + } + // 看来要递归 + else { + NutMap map2 = obj2nutmap(v); + re.put(en.getKey(), map2); + } + } + return re; + } + + @SuppressWarnings("unchecked") + private static void __join_ele_to_list_as_map(List list, + Object o, + final Map memo) { + if (null == o) { + return; + } + + // 如果是 Map,就直接 putAll 一下咯 + if (o instanceof Map) { + NutMap map2 = __change_map_to_nutmap((Map) o, memo); + list.add(map2); + return; + } + + Mirror mr = Mirror.me(o); + // 普通值 + if (mr.isSimple()) { + list.add(o); + } + // 已经输出过了 + else if (memo.containsKey(o)) { + list.add(null); + } + // 数组或者集合 + else if (mr.isColl()) { + final List list2 = new ArrayList(Lang.length(o)); + Lang.each(o, new Each() { + public void invoke(int index, Object ele, int length) { + __join_ele_to_list_as_map(list2, ele, memo); + } + }); + list.add(list2); + } + // Map + else if (mr.isMap()) { + NutMap map2 = __change_map_to_nutmap((Map) o, memo); + list.add(map2); + } + // 看来要递归 + else { + NutMap map = obj2nutmap(o); + list.add(map); + } + } + + /** + * 将对象转换成 Map + * + * @param obj + * POJO 对象 + * @return Map 对象 + */ + @SuppressWarnings("unchecked") + public static Map obj2map(Object obj) { + return obj2map(obj, HashMap.class); + } + + /** + * 将对象转为 Nutz 的标准 Map 封装 + * + * @param obj + * POJO du对象 + * @return NutMap 对象 + */ + public static NutMap obj2nutmap(Object obj) { + return obj2map(obj, NutMap.class); + } + + /** + * 将对象转换成 Map + * + * @param + * @param obj + * POJO 对象 + * @param mapType + * Map 的类型 + * @return Map 对象 + */ + public static > T obj2map(Object obj, Class mapType) { + try { + T map = mapType.newInstance(); + Lang.obj2map(obj, map, new HashMap()); + return map; + } + catch (Exception e) { + throw Lang.wrapThrow(e); + } + } + + /** + * 返回一个集合对象的枚举对象。实际上就是对 Iterator 接口的一个封装 + * + * @param col + * 集合对象 + * @return 枚举对象 + */ + public static Enumeration enumeration(Collection col) { + final Iterator it = col.iterator(); + return new Enumeration() { + public boolean hasMoreElements() { + return it.hasNext(); + } + + public T nextElement() { + return it.next(); + } + }; + } + + /** + * 将枚举对象,变成集合 + * + * @param enums + * 枚举对象 + * @param cols + * 集合对象 + * @return 集合对象 + */ + public static , E> T enum2collection(Enumeration enums, T cols) { + while (enums.hasMoreElements()) + cols.add(enums.nextElement()); + return cols; + } + + /** + * 将字符数组强制转换成字节数组。如果字符为双字节编码,则会丢失信息 + * + * @param cs + * 字符数组 + * @return 字节数组 + */ + public static byte[] toBytes(char[] cs) { + byte[] bs = new byte[cs.length]; + for (int i = 0; i < cs.length; i++) + bs[i] = (byte) cs[i]; + return bs; + } + + /** + * 将整数数组强制转换成字节数组。整数的高位将会被丢失 + * + * @param is + * 整数数组 + * @return 字节数组 + */ + public static byte[] toBytes(int[] is) { + byte[] bs = new byte[is.length]; + for (int i = 0; i < is.length; i++) + bs[i] = (byte) is[i]; + return bs; + } + + /** + * 判断当前系统是否为Windows + * + * @return true 如果当前系统为Windows系统 + */ + public static boolean isWin() { + try { + String os = System.getenv("OS"); + return os != null && os.indexOf("Windows") > -1; + } + catch (Throwable e) { + return false; + } + } + + /** + * 原方法使用线程ClassLoader,各种问题,改回原版. + */ + public static Class loadClass(String className) throws ClassNotFoundException { + try { + return Thread.currentThread().getContextClassLoader().loadClass(className); + } + catch (Throwable e) { + return Class.forName(className); + } + } + + /** + * 当前运行的 Java 虚拟机是 JDK6 及更高版本的话,则返回 true + * + * @return true 如果当前运行的 Java 虚拟机是 JDK6 + */ + public static boolean isJDK6() { + return JdkTool.getMajorVersion() >= 6; + } + + /** + * 获取基本类型的默认值 + * + * @param pClass + * 基本类型 + * @return 0/false,如果传入的pClass不是基本类型的类,则返回null + */ + public static Object getPrimitiveDefaultValue(Class pClass) { + if (int.class.equals(pClass)) + return Integer.valueOf(0); + if (long.class.equals(pClass)) + return Long.valueOf(0); + if (short.class.equals(pClass)) + return Short.valueOf((short) 0); + if (float.class.equals(pClass)) + return Float.valueOf(0f); + if (double.class.equals(pClass)) + return Double.valueOf(0); + if (byte.class.equals(pClass)) + return Byte.valueOf((byte) 0); + if (char.class.equals(pClass)) + return Character.valueOf((char) 0); + if (boolean.class.equals(pClass)) + return Boolean.FALSE; + return null; + } + + /** + * 当一个类使用来定义泛型时,本方法返回类的一个字段的具体类型。 + * + * @param me + * @param field + */ + public static Type getFieldType(Mirror me, String field) throws NoSuchFieldException { + return getFieldType(me, me.getField(field)); + } + + /** + * 当一个类使用 来定义泛型时, 本方法返回类的一个方法所有参数的具体类型 + * + * @param me + * @param method + */ + public static Type[] getMethodParamTypes(Mirror me, Method method) { + Type[] types = method.getGenericParameterTypes(); + List ts = new ArrayList(); + for (Type type : types) { + ts.add(getGenericsType(me, type)); + } + return ts.toArray(new Type[ts.size()]); + } + + /** + * 当一个类使用来定义泛型时,本方法返回类的一个字段的具体类型。 + * + * @param me + * @param field + */ + public static Type getFieldType(Mirror me, Field field) { + Type type = field.getGenericType(); + return getGenericsType(me, type); + } + + /** + * 当一个类使用来定义泛型时,本方法返回类的一个字段的具体类型。 + * + * @param me + * @param type + */ + public static Type getGenericsType(Mirror me, Type type) { + Type[] types = me.getGenericsTypes(); + Type t = type; + if (type instanceof TypeVariable && types != null && types.length > 0) { + Type[] tvs = me.getType().getTypeParameters(); + for (int i = 0; i < tvs.length; i++) { + if (type.equals(tvs[i])) { + type = me.getGenericsType(i); + break; + } + } + } + if (!type.equals(t)) { + return type; + } + if (types != null && types.length > 0 && type instanceof ParameterizedType) { + ParameterizedType pt = (ParameterizedType) type; + + if (pt.getActualTypeArguments().length >= 0) { + NutType nt = new NutType(); + nt.setOwnerType(pt.getOwnerType()); + nt.setRawType(pt.getRawType()); + Type[] tt = new Type[pt.getActualTypeArguments().length]; + for (int i = 0; i < tt.length; i++) { + tt[i] = types[i]; + } + nt.setActualTypeArguments(tt); + return nt; + } + } + + return type; + } + + /** + * 获取一个 Type 类型实际对应的Class + * + * @param type + * 类型 + * @return 与Type类型实际对应的Class + */ + @SuppressWarnings("rawtypes") + public static Class getTypeClass(Type type) { + Class clazz = null; + if (type instanceof Class) { + clazz = (Class) type; + } else if (type instanceof ParameterizedType) { + ParameterizedType pt = (ParameterizedType) type; + clazz = (Class) pt.getRawType(); + } else if (type instanceof GenericArrayType) { + GenericArrayType gat = (GenericArrayType) type; + Class typeClass = getTypeClass(gat.getGenericComponentType()); + return Array.newInstance(typeClass, 0).getClass(); + } else if (type instanceof TypeVariable) { + TypeVariable tv = (TypeVariable) type; + Type[] ts = tv.getBounds(); + if (ts != null && ts.length > 0) + return getTypeClass(ts[0]); + } else if (type instanceof WildcardType) { + WildcardType wt = (WildcardType) type; + Type[] t_low = wt.getLowerBounds();// 取其下界 + if (t_low.length > 0) + return getTypeClass(t_low[0]); + Type[] t_up = wt.getUpperBounds(); // 没有下界?取其上界 + return getTypeClass(t_up[0]);// 最起码有Object作为上界 + } + return clazz; + } + + /** + * 返回一个 Type 的泛型数组, 如果没有, 则直接返回null + * + * @param type + * 类型 + * @return 一个 Type 的泛型数组, 如果没有, 则直接返回null + */ + public static Type[] getGenericsTypes(Type type) { + if (type instanceof ParameterizedType) { + ParameterizedType pt = (ParameterizedType) type; + return pt.getActualTypeArguments(); + } + return null; + } + + /** + * 强制从字符串转换成一个 Class,将 ClassNotFoundException 包裹成 RuntimeException + * + * @param + * @param name + * 类名 + * @param type + * 这个类型的边界 + * @return 类对象 + */ + @SuppressWarnings("unchecked") + public static Class forName(String name, Class type) { + Class re; + try { + re = Lang.loadClass(name); + return (Class) re; + } + catch (ClassNotFoundException e) { + throw Lang.wrapThrow(e); + } + } + + /** + * 获取指定文件的 MD5 值 + * + * @param f + * 文件 + * @return 指定文件的 MD5 值 + * @see #digest(String, File) + */ + public static String md5(File f) { + return digest("MD5", f); + } + + /** + * 获取指定输入流的 MD5 值 + * + * @param ins + * 输入流 + * @return 指定输入流的 MD5 值 + * @see #digest(String, InputStream) + */ + public static String md5(InputStream ins) { + return digest("MD5", ins); + } + + /** + * 获取指定字符串的 MD5 值 + * + * @param cs + * 字符串 + * @return 指定字符串的 MD5 值 + * @see #digest(String, CharSequence) + */ + public static String md5(CharSequence cs) { + return digest("MD5", cs); + } + + /** + * 获取指定文件的 SHA1 值 + * + * @param f + * 文件 + * @return 指定文件的 SHA1 值 + * @see #digest(String, File) + */ + public static String sha1(File f) { + return digest("SHA1", f); + } + + /** + * 获取指定输入流的 SHA1 值 + * + * @param ins + * 输入流 + * @return 指定输入流的 SHA1 值 + * @see #digest(String, InputStream) + */ + public static String sha1(InputStream ins) { + return digest("SHA1", ins); + } + + /** + * 获取指定字符串的 SHA1 值 + * + * @param cs + * 字符串 + * @return 指定字符串的 SHA1 值 + * @see #digest(String, CharSequence) + */ + public static String sha1(CharSequence cs) { + return digest("SHA1", cs); + } + + /** + * 获取指定文件的 SHA256 值 + * + * @param f + * 文件 + * @return 指定文件的 SHA256 值 + * @see #digest(String, File) + */ + public static String sha256(File f) { + return digest("SHA-256", f); + } + + /** + * 获取指定输入流的 SHA256 值 + * + * @param ins + * 输入流 + * @return 指定输入流的 SHA256 值 + * @see #digest(String, InputStream) + */ + public static String sha256(InputStream ins) { + return digest("SHA-256", ins); + } + + /** + * 获取指定字符串的 SHA256 值 + * + * @param cs + * 字符串 + * @return 指定字符串的 SHA256 值 + * @see #digest(String, CharSequence) + */ + public static String sha256(CharSequence cs) { + return digest("SHA-256", cs); + } + + /** + * 从数据文件计算出数字签名 + * + * @param algorithm + * 算法,比如 "SHA1" "SHA-256" 或者 "MD5" 等 + * @param f + * 文件 + * @return 数字签名 + */ + public static String digest(String algorithm, File f) { + return digest(algorithm, Streams.fileIn(f)); + } + + /** + * 从流计算出数字签名,计算完毕流会被关闭 + * + * @param algorithm + * 算法,比如 "SHA1" 或者 "MD5" 等 + * @param ins + * 输入流 + * @return 数字签名 + */ + public static String digest(String algorithm, InputStream ins) { + return fixedHexString(digest2(algorithm, ins)); + } + + public static byte[] digest2(String algorithm, InputStream ins) { + try { + MessageDigest md = MessageDigest.getInstance(algorithm); + + byte[] bs = new byte[HASH_BUFF_SIZE]; + int len = 0; + while ((len = ins.read(bs)) != -1) { + md.update(bs, 0, len); + } + + return md.digest(); + } + catch (NoSuchAlgorithmException e) { + throw Lang.wrapThrow(e); + } + catch (IOException e) { + throw Lang.wrapThrow(e); + } + finally { + Streams.safeClose(ins); + } + } + + public static String digest(String algorithm, InputStream ins, byte[] keyBytes) { + return fixedHexString(digest2(algorithm, ins, keyBytes)); + } + + public static String digest(String algorithm, File f, byte[] keyBytes) { + return fixedHexString(digest2(algorithm, Streams.fileIn(f), keyBytes)); + } + + public static String digest(String algorithm, String str, byte[] keyBytes) { + return fixedHexString(digest2(algorithm, Streams.wrap(str.getBytes()), keyBytes)); + } + + public static byte[] digest2(String algorithm, InputStream ins, byte[] keyBytes) { + try { + SecretKeySpec signingKey = new SecretKeySpec(keyBytes, "Hmac" + algorithm); + Mac mac = Mac.getInstance("Hmac" + algorithm); + mac.init(signingKey); + + byte[] bs = new byte[HASH_BUFF_SIZE]; + int len = 0; + while ((len = ins.read(bs)) != -1) { + mac.update(bs, 0, len); + } + + return mac.doFinal(); + } + catch (NoSuchAlgorithmException e) { + throw Lang.wrapThrow(e); + } + catch (IOException e) { + throw Lang.wrapThrow(e); + } catch (InvalidKeyException e) { + throw Lang.wrapThrow(e); + } + finally { + Streams.safeClose(ins); + } + } + + /** + * 从字符串计算出数字签名 + * + * @param algorithm + * 算法,比如 "SHA1" 或者 "MD5" 等 + * @param cs + * 字符串 + * @return 数字签名 + */ + public static String digest(String algorithm, CharSequence cs) { + return digest(algorithm, Strings.getBytesUTF8(null == cs ? "" : cs), null, 1); + } + + /** + * 从字节数组计算出数字签名 + * + * @param algorithm + * 算法,比如 "SHA1" 或者 "MD5" 等 + * @param bytes + * 字节数组 + * @param salt + * 随机字节数组 + * @param iterations + * 迭代次数 + * @return 数字签名 + */ + public static String digest(String algorithm, byte[] bytes, byte[] salt, int iterations) { + try { + MessageDigest md = MessageDigest.getInstance(algorithm); + + if (salt != null) { + md.update(salt); + } + + byte[] hashBytes = md.digest(bytes); + + for (int i = 1; i < iterations; i++) { + md.reset(); + hashBytes = md.digest(hashBytes); + } + + return fixedHexString(hashBytes); + } + catch (NoSuchAlgorithmException e) { + throw Lang.wrapThrow(e); + } + } + + /** 当前运行的 Java 虚拟机是否是在安卓环境 */ + public static final boolean isAndroid; + + static { + boolean flag = false; + try { + Class.forName("android.Manifest"); + flag = true; + } + catch (Throwable e) {} + isAndroid = flag; + } + + /** + * 将指定的数组的内容倒序排序。注意,这会破坏原数组的内容 + * + * @param arrays + * 指定的数组 + */ + public static void reverse(T[] arrays) { + int size = arrays.length; + for (int i = 0; i < size; i++) { + int ih = i; + int it = size - 1 - i; + if (ih == it || ih > it) { + break; + } + T ah = arrays[ih]; + T swap = arrays[it]; + arrays[ih] = swap; + arrays[it] = ah; + } + } + + @Deprecated + public static String simpleMetodDesc(Method method) { + return simpleMethodDesc(method); + } + + public static String simpleMethodDesc(Method method) { + return String.format("%s.%s(...)", + method.getDeclaringClass().getSimpleName(), + method.getName()); + } + + public static String fixedHexString(byte[] hashBytes) { + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < hashBytes.length; i++) { + sb.append(Integer.toString((hashBytes[i] & 0xff) + 0x100, 16).substring(1)); + } + + return sb.toString(); + } + + /** + * 一个便利的方法,将当前线程睡眠一段时间 + * + * @param ms + * 要睡眠的时间 ms + */ + public static void sleep(long ms) { + try { + Thread.sleep(ms); + } + catch (InterruptedException e) { + throw Lang.wrapThrow(e); + } + } + + /** + * 一个便利的等待方法同步一个对象 + * + * @param lock + * 锁对象 + * @param ms + * 要等待的时间 ms + */ + public static void wait(Object lock, long ms) { + if (null != lock) + synchronized (lock) { + try { + lock.wait(ms); + } + catch (InterruptedException e) { + throw Lang.wrapThrow(e); + } + } + } + + /** + * 通知对象的同步锁 + * + * @param lock + * 锁对象 + */ + public static void notifyAll(Object lock) { + if (null != lock) + synchronized (lock) { + lock.notifyAll(); + } + } + + public static void runInAnThread(Runnable runnable) { + new Thread(runnable).start(); + } + + /** + * map对象浅过滤,返回值是一个新的map + * + * @param source + * 原始的map对象 + * @param prefix + * 包含什么前缀,并移除前缀 + * @param include + * 正则表达式 仅包含哪些key(如果有前缀要求,则已经移除了前缀) + * @param exclude + * 正则表达式 排除哪些key(如果有前缀要求,则已经移除了前缀) + * @param keyMap + * 映射map, 原始key--目标key (如果有前缀要求,则已经移除了前缀) + * @return 经过过滤的map,与原始map不是同一个对象 + */ + public static Map filter(Map source, + String prefix, + String include, + String exclude, + Map keyMap) { + LinkedHashMap dst = new LinkedHashMap(); + if (source == null || source.isEmpty()) + return dst; + + Pattern includePattern = include == null ? null : Regex.getPattern(include); + Pattern excludePattern = exclude == null ? null : Regex.getPattern(exclude); + + for (Entry en : source.entrySet()) { + String key = en.getKey(); + if (prefix != null) { + if (key.startsWith(prefix)) + key = key.substring(prefix.length()); + else + continue; + } + if (includePattern != null && !includePattern.matcher(key).find()) + continue; + if (excludePattern != null && excludePattern.matcher(key).find()) + continue; + if (keyMap != null && keyMap.containsKey(key)) + dst.put(keyMap.get(key), en.getValue()); + else + dst.put(key, en.getValue()); + } + return dst; + } + + /** + * 获得访问者的IP地址, 反向代理过的也可以获得 + * + * @param request + * 请求的req对象 + * @return 来源ip + */ + public static String getIP(HttpServletRequest request) { + if (request == null) + return ""; + String ip = request.getHeader("X-Forwarded-For"); + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("HTTP_CLIENT_IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("HTTP_X_FORWARDED_FOR"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); + } + } else if (ip.length() > 15) { + String[] ips = ip.split(","); + for (int index = 0; index < ips.length; index++) { + String strIp = ips[index]; + if (!("unknown".equalsIgnoreCase(strIp))) { + ip = strIp; + break; + } + } + } + if (Strings.isBlank(ip)) + return ""; + if (isIPv4Address(ip) || isIPv6Address(ip)) { + return ip; + } + return ""; + } + + /** + * @return 返回当前程序运行的根目录 + */ + public static String runRootPath() { + String cp = Lang.class.getClassLoader().getResource("").toExternalForm(); + if (cp.startsWith("file:")) { + cp = cp.substring("file:".length()); + } + return cp; + } + + public static T copyProperties(Object origin, T target) { + return copyProperties(origin, target, null, null, false, true); + } + + public static T copyProperties(Object origin, + T target, + String active, + String lock, + boolean ignoreNull, + boolean ignoreStatic) { + if (origin == null) + throw new IllegalArgumentException("origin is null"); + if (target == null) + throw new IllegalArgumentException("target is null"); + Pattern at = active == null ? null : Regex.getPattern(active); + Pattern lo = lock == null ? null : Regex.getPattern(lock); + Mirror originMirror = Mirror.me(origin); + Mirror targetMirror = Mirror.me(target); + Field[] fields = targetMirror.getFields(); + for (Field field : originMirror.getFields()) { + String name = field.getName(); + if (at != null && !at.matcher(name).find()) + continue; + if (lo != null && lo.matcher(name).find()) + continue; + if (ignoreStatic && Modifier.isStatic(field.getModifiers())) + continue; + Object val = originMirror.getValue(origin, field); + if (ignoreNull && val == null) + continue; + for (Field _field : fields) { + if (_field.getName().equals(field.getName())) { + targetMirror.setValue(target, _field, val); + } + } + // TODO 支持getter/setter比对 + } + return target; + } + + public static StringBuilder execOutput(String cmd) throws IOException { + return execOutput(Strings.splitIgnoreBlank(cmd, " "), Encoding.CHARSET_UTF8); + } + + public static StringBuilder execOutput(String cmd, Charset charset) throws IOException { + return execOutput(Strings.splitIgnoreBlank(cmd, " "), charset); + } + + public static StringBuilder execOutput(String cmd[]) throws IOException { + return execOutput(cmd, Encoding.CHARSET_UTF8); + } + + public static StringBuilder execOutput(String[] cmd, Charset charset) throws IOException { + Process p = Runtime.getRuntime().exec(cmd); + p.getOutputStream().close(); + InputStreamReader r = new InputStreamReader(p.getInputStream(), charset); + StringBuilder sb = new StringBuilder(); + Streams.readAndClose(r, sb); + return sb; + } + + public static void exec(String cmd, StringBuilder out, StringBuilder err) throws IOException { + exec(Strings.splitIgnoreBlank(cmd, " "), Encoding.CHARSET_UTF8, out, err); + } + + public static void exec(String[] cmd, StringBuilder out, StringBuilder err) throws IOException { + exec(cmd, Encoding.CHARSET_UTF8, out, err); + } + + public static void exec(String[] cmd, Charset charset, StringBuilder out, StringBuilder err) + throws IOException { + Process p = Runtime.getRuntime().exec(cmd); + p.getOutputStream().close(); + InputStreamReader sOut = new InputStreamReader(p.getInputStream(), charset); + Streams.readAndClose(sOut, out); + + InputStreamReader sErr = new InputStreamReader(p.getErrorStream(), charset); + Streams.readAndClose(sErr, err); + } + + public static Class loadClassQuite(String className) { + try { + return loadClass(className); + } + catch (ClassNotFoundException e) { + return null; + } + } + + public static byte[] toBytes(Object obj) { + try { + ByteArrayOutputStream bao = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bao); + oos.writeObject(obj); + return bao.toByteArray(); + } + catch (IOException e) { + return null; + } + } + + @SuppressWarnings("unchecked") + public static T fromBytes(byte[] buf, Class klass) { + try { + return (T) new ObjectInputStream(new ByteArrayInputStream(buf)).readObject(); + } + catch (ClassNotFoundException e) { + return null; + } + catch (IOException e) { + return null; + } + } + + public static class JdkTool { + public static String getVersionLong() { + Properties sys = System.getProperties(); + return sys.getProperty("java.version"); + } + + public static int getMajorVersion() { + String ver = getVersionLong(); + if (Strings.isBlank(ver)) + return 6; + String[] tmp = ver.split("\\."); + if (tmp.length < 2) + return 6; + int t = Integer.parseInt(tmp[0]); + if (t > 1) + return t; + return Integer.parseInt(tmp[1]); + } + + public static boolean isEarlyAccess() { + String ver = getVersionLong(); + if (Strings.isBlank(ver)) + return false; + return ver.contains("-ea"); + } + + /** + * 获取进程id + * + * @param fallback + * 如果获取失败,返回什么呢? + * @return 进程id + */ + public static String getProcessId(final String fallback) { + final String jvmName = ManagementFactory.getRuntimeMXBean().getName(); + final int index = jvmName.indexOf('@'); + if (index < 1) { + return fallback; + } + try { + return Long.toString(Long.parseLong(jvmName.substring(0, index))); + } + catch (NumberFormatException e) {} + return fallback; + } + } + + /** + * 判断一个对象是否不为空。它支持如下对象类型: + *
    + *
  • null : 一定为空 + *
  • 数组 + *
  • 集合 + *
  • Map + *
  • 其他对象 : 一定不为空 + *
+ * + * @param obj + * 任意对象 + * @return 是否为空 + */ + public static boolean isNotEmpty(Object obj) { + return !isEmpty(obj); + } + + /** + * 获取指定字符串的 HmacMD5 值 + * + * @param data + * 字符串 + * @param secret + * 密钥 + * @return 指定字符串的 HmacMD5 值 + */ + public static String hmacmd5(String data, String secret) { + if (isEmpty(data)) + throw new NullPointerException("data is null"); + if (isEmpty(secret)) + throw new NullPointerException("secret is null"); + byte[] bytes = null; + try { + SecretKey secretKey = new SecretKeySpec(secret.getBytes(Encoding.UTF8), "HmacMD5"); + Mac mac = Mac.getInstance(secretKey.getAlgorithm()); + mac.init(secretKey); + bytes = mac.doFinal(data.getBytes(Encoding.UTF8)); + } + catch (Exception e) { + e.printStackTrace(); + throw Lang.wrapThrow(e); + } + return fixedHexString(bytes); + } + + /** + * 获取指定字符串的 HmacSHA256 值 + * + * @param data + * 字符串 + * @param secret + * 密钥 + * @return 指定字符串的 HmacSHA256 值 + */ + public static String hmacSHA256(String data, String secret) { + if (isEmpty(data)) + throw new NullPointerException("data is null"); + if (isEmpty(secret)) + throw new NullPointerException("secret is null"); + byte[] bytes = null; + try { + SecretKey secretKey = new SecretKeySpec(secret.getBytes(Encoding.UTF8), "HmacSHA256"); + Mac mac = Mac.getInstance(secretKey.getAlgorithm()); + mac.init(secretKey); + bytes = mac.doFinal(data.getBytes(Encoding.UTF8)); + } + catch (Exception e) { + e.printStackTrace(); + throw Lang.wrapThrow(e); + } + return fixedHexString(bytes); + } + + /** + * 获取指定字符串的 HmacSHA256 值 + * + * @param data + * 字符串 + * @param secret + * 密钥 + * @return 指定字符串的 HmacSHA256 值 + */ + public static String hmacSHA1(String data, String secret) { + if (isEmpty(data)) + throw new NullPointerException("data is null"); + if (isEmpty(secret)) + throw new NullPointerException("secret is null"); + byte[] bytes = null; + try { + SecretKey secretKey = new SecretKeySpec(secret.getBytes(Encoding.UTF8), "HmacSHA1"); + Mac mac = Mac.getInstance(secretKey.getAlgorithm()); + mac.init(secretKey); + bytes = mac.doFinal(data.getBytes(Encoding.UTF8)); + } + catch (Exception e) { + e.printStackTrace(); + throw Lang.wrapThrow(e); + } + return fixedHexString(bytes); + } + +} diff --git a/src/org/nutz/lang/Maths.java b/src/org/nutz/lang/Maths.java index 456f0435df..ef550da7a5 100644 --- a/src/org/nutz/lang/Maths.java +++ b/src/org/nutz/lang/Maths.java @@ -14,11 +14,13 @@ public abstract class Maths { /** * 返回最大的一个 * - * @param nums 需要比较的数组 + * @param nums + * 需要比较的数组 * @return 最大值 */ public static int max(int... nums) { return takeOne(new CompareSomeThing() { + @Override public boolean compare(int arg0, int arg1) { return arg0 > arg1; } @@ -28,11 +30,13 @@ public boolean compare(int arg0, int arg1) { /** * 返回最小的一个 * - * @param nums 需要比较的数组 + * @param nums + * 需要比较的数组 * @return 最小值 */ public static int min(int... nums) { return takeOne(new CompareSomeThing() { + @Override public boolean compare(int arg0, int arg1) { return arg0 < arg1; } @@ -105,7 +109,7 @@ public static boolean isMaskAll(int bs, int mask) { * @param low * the low bit position (inclusive), 0 base * @param high - * the hight bit position (exclusive), 0 base + * the high bit position (exclusive), 0 base * @return new integer */ public static int extract(int bs, int low, int high) { @@ -120,7 +124,8 @@ public static int extract(int bs, int low, int high) { /** * 获得字符数组的全排列 * - * @param arr 字符数组 + * @param arr + * 字符数组 * @return 全排列 */ public static String[] permutation(char... arr) { @@ -130,7 +135,8 @@ public static String[] permutation(char... arr) { /** * 按照指定长度, 获得字符数组的全排列 * - * @param arr 字符数组 + * @param arr + * 字符数组 * @return 全排列 */ public static String[] permutation(int length, char... arr) { @@ -143,6 +149,36 @@ public static String[] permutation(int length, char... arr) { return slist.toArray(new String[]{}); } + /** + * 坐标点旋转计算方法。 + * + * 坐标点(x1,y1)绕另一个坐标点(x2,y2)旋转角度(a)后的新坐标 + * + * + * @param x1 + * 被计算点横坐标 + * @param y1 + * 被计算点纵坐标 + * @param x2 + * 圆心横坐标 + * @param y2 + * 圆心纵坐标 + * @param a + * 角度 + * @return (x3,y3) + */ + public static int[] rotateXY(int x1, int y1, int x2, int y2, double a) { + double l = (a * Math.PI) / 180; + + double cosv = Math.cos(l); + double sinv = Math.sin(l); + + int newX = (int) ((x1 - x2) * cosv - (y1 - y2) * sinv + x2); + int newY = (int) ((x1 - x2) * sinv + (y1 - y2) * cosv + y2); + + return new int[]{newX, newY}; + } + // --------------------------- 以下为几个辅助方法 private static void getCombination(List slist, diff --git a/src/org/nutz/lang/Mirror.java b/src/org/nutz/lang/Mirror.java index 2356479020..b9921709a2 100644 --- a/src/org/nutz/lang/Mirror.java +++ b/src/org/nutz/lang/Mirror.java @@ -9,6 +9,8 @@ import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.temporal.TemporalAccessor; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; @@ -44,14 +46,15 @@ * @param */ public class Mirror { - + @SuppressWarnings("rawtypes") static Map mirrorCache = new HashMap(); - + protected BornContext emtryArgsBornContext; private static class DefaultTypeExtractor implements TypeExtractor { + @Override public Class[] extract(Mirror mirror) { Class theType = mirror.getType(); List> re = new ArrayList>(5); @@ -130,12 +133,14 @@ public static Mirror me(Class classOfT) { * 对象。 * @return Mirror, 如果 对象 null,则返回 null */ - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "rawtypes"}) public static Mirror me(T obj) { if (obj == null) return null; if (obj instanceof Class) return (Mirror) me((Class) obj); + if (obj instanceof Enum) + return me(((Enum) obj).getDeclaringClass()); return (Mirror) me(obj.getClass()); } @@ -318,9 +323,9 @@ public static void evalGetterSetter(Method method, getter = method; // 寻找 setter try { - setter = method.getDeclaringClass().getMethod("set" - + Strings.upperFirst(name), - method.getReturnType()); + setter = method.getDeclaringClass() + .getMethod("set" + Strings.upperFirst(name), + method.getReturnType()); } catch (Exception e) {} @@ -333,9 +338,9 @@ else if (name.startsWith("is") getter = method; // 寻找 setter try { - setter = method.getDeclaringClass().getMethod("set" - + Strings.upperFirst(name), - method.getReturnType()); + setter = method.getDeclaringClass() + .getMethod("set" + Strings.upperFirst(name), + method.getReturnType()); } catch (Exception e) {} } @@ -376,6 +381,7 @@ public static void evalGetterSetter(final Method method, final String errmsgFormat, Callback3 callback) { evalGetterSetter(method, callback, new Callback() { + @Override public void invoke(Method method) { throw Lang.makeThrow(errmsgFormat, method.getName(), @@ -526,7 +532,7 @@ public Field[] getFields(Class ann) { } /** - * 获得当前类以及所有父类的所有的属性,包括私有属性。
+ * 获得当前类以及所有父类的所有的属性,包括私有属性,但不包括 final和static 属性
* 但是父类不包括 Object 类,并且,如果子类的属性如果与父类重名,将会将其覆盖 * * @return 属性列表 @@ -535,6 +541,21 @@ public Field[] getFields() { return _getFields(true, false, true, true); } + /** + * 获得当前类以及所有父类的所有的属性,包括私有属性。
+ * 但是父类不包括 Object 类,并且,如果子类的属性如果与父类重名,将会将其覆盖 + * + * @param noStatic + * 返回的列表中是否包括 static 标记的属性, true: 不包括。false:包括 + * + * @param noFinal + * 返回的列表中是否包括 final 标记的属性, true: 不包括。false:包括 + * @return 属性列表 + */ + public Field[] getFields(boolean noStatic, boolean noFinal) { + return _getFields(noStatic, false, noFinal, true); + } + /** * 获得所有的静态变量属性 * @@ -591,7 +612,7 @@ public A getAnnotation(Class annType) { do { ann = cc.getAnnotation(annType); cc = cc.getSuperclass(); - } while (null == ann && cc != Object.class); + } while (null == ann && null != cc && cc != Object.class); return ann; } @@ -826,14 +847,13 @@ public Object getValue(Object obj, String name) throws FailToGetValueException { return Lang.eleSize(obj); } if (obj instanceof Map) { - return ((Map)obj).get(name); + return ((Map) obj).get(name); } if (obj instanceof List) { try { - return ((List)obj).get(Integer.parseInt(name)); - } - catch (Exception e2) { + return ((List) obj).get(Integer.parseInt(name)); } + catch (Exception e2) {} } } throw makeGetValueException(obj == null ? getType() : obj.getClass(), name, e); @@ -1001,8 +1021,7 @@ public T born(Object... args) { if (emtryArgsBornContext == null) emtryArgsBornContext = Borns.eval(klass, args); bc = emtryArgsBornContext; - } - else + } else bc = Borns.eval(klass, args); if (null == bc) throw new BorningException(klass, args); @@ -1146,7 +1165,8 @@ public Method findMethod(String name, Object[] args) throws NoSuchMethodExceptio * @param paramTypes * 参数类型列表 * @return 方法 - * @throws NoSuchMethodException 找不到合适方法时抛出 + * @throws NoSuchMethodException + * 找不到合适方法时抛出 */ public Method findMethod(String name, Class... paramTypes) throws NoSuchMethodException { try { @@ -1210,7 +1230,8 @@ else if (m.getParameterTypes().length == argNumber) * @param paramTypes * 参数个数 * @return 方法 - * @throws NoSuchMethodException 找不到合适的方法时抛出 + * @throws NoSuchMethodException + * 找不到合适的方法时抛出 */ public Method findMethod(Class returnType, Class... paramTypes) throws NoSuchMethodException { @@ -1457,7 +1478,7 @@ public boolean isInterface() { } /** - * @return 当前对象是否为小数 (float, dobule) + * @return 当前对象是否为小数 (float, double) */ public boolean isDecimal() { return isFloat() || isDouble(); @@ -1617,6 +1638,25 @@ public boolean isDateTimeLike() { || java.sql.Time.class.isAssignableFrom(klass); } + public boolean isLocalDateLike() { + try { + return NutConf.HAS_LOCAL_DATE_TIME && is(LocalDate.class); + } + catch (Exception e) { + return false; + } + } + + public boolean isLocalDateTimeLike() { + try { + return NutConf.HAS_LOCAL_DATE_TIME && TemporalAccessor.class.isAssignableFrom(klass); + } + catch (Exception e) { + return false; + } + } + + @Override public String toString() { return klass.getName(); } @@ -1860,9 +1900,9 @@ public boolean hasInterface(Class clzInterface) { return false; } - @SuppressWarnings({"unchecked", "rawtypes"}) - public static T getAnnotationDeep(Method method, Class annotationClass) { + public static T getAnnotationDeep(Method method, + Class annotationClass) { T t = method.getAnnotation(annotationClass); if (t != null) return t; @@ -1889,7 +1929,8 @@ public static T getAnnotationDeep(Method method, Class } } } - } catch (Exception e) {} + } + catch (Exception e) {} klass = klass.getSuperclass(); } for (Class klass2 : method.getDeclaringClass().getInterfaces()) { @@ -1898,12 +1939,14 @@ public static T getAnnotationDeep(Method method, Class t = tmp.getAnnotation(annotationClass); if (t != null) return t; - } catch (Exception e) {} + } + catch (Exception e) {} } return null; } - public static T getAnnotationDeep(Class type, Class annotationClass) { + public static T getAnnotationDeep(Class type, + Class annotationClass) { Class cc = type; T t; do { @@ -1923,7 +1966,8 @@ public static T getAnnotationDeep(Class type, Class return null; } - public static boolean isAnnotationExists(Method method, Class... classes) { + public static boolean isAnnotationExists(Method method, + Class... classes) { if (!Lang.isEmptyArray(classes)) { for (Class klass : classes) { if (getAnnotationDeep(method, klass) != null) @@ -1933,7 +1977,8 @@ public static boolean isAnnotationExists(Method method, Class type, Class... classes) { + public static boolean isAnnotationExists(Class type, + Class... classes) { if (!Lang.isEmptyArray(classes)) { for (Class klass : classes) { if (getAnnotationDeep(type, klass) != null) diff --git a/src/org/nutz/lang/Nums.java b/src/org/nutz/lang/Nums.java index 75ebd276ff..377faa8100 100644 --- a/src/org/nutz/lang/Nums.java +++ b/src/org/nutz/lang/Nums.java @@ -3,6 +3,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.nutz.lang.util.Regex; + /** * 关于数的一些帮助函数 * @@ -10,6 +12,133 @@ */ public abstract class Nums { + /** + * 对齐浮点数精度,超过精度四舍五入 + * + * @param n + * 浮点数 + * @param dp + * 精度 + * @return 对齐精度的浮点数 + */ + public static float precision(float n, int dp) { + if (0 == dp) { + return Math.round(n); + } + if (1 == dp) { + return Math.round(n * 10f) / 10f; + } + if (2 == dp) { + return Math.round(n * 100f) / 100f; + } + if (3 == dp) { + return Math.round(n * 1000f) / 1000f; + } + // 其他精度 + float p = (float) Math.pow(10, dp); + return Math.round(n * p) / p; + } + + /** + * 对齐双精度浮点精度,超过精度四舍五入 + * + * @param n + * 双精度浮点数 + * @param dp + * 精度 + * @return 对齐精度的双精度浮点数 + */ + public static double precision(double n, int dp) { + if (0 == dp) { + return Math.round(n); + } + if (1 == dp) { + return Math.round(n * 10) / 10.0; + } + if (2 == dp) { + return Math.round(n * 100) / 100.0; + } + if (3 == dp) { + return Math.round(n * 1000) / 1000.0; + } + // 其他精度 + double p = Math.pow(10, dp); + return Math.round(n * p) / p; + } + + /** + * @param a + * 数字 + * @param b + * 数字 + * @return 两个数的最大公约数 greatest common divisor(gcd) + */ + public static int gcd(int a, int b) { + a = Math.round(a); + b = Math.round(b); + if (b != 0) { + return gcd(b, a % b); + } + return a; + } + + /** + * @param list + * 一组整数 + * @return 一组整数的最大公约数 greatest common divisor(gcd) + */ + public static int gcds(int... list) { + // 没数 + if (list.length == 0) + return Integer.MIN_VALUE; + // 一个是自己 + if (list.length == 1) { + return list[0]; + } + // 两个以上 + int gcd = gcd(list[0], list[1]); + for (int i = 2; i < list.length; i++) { + gcd = gcd(gcd, list[i]); + } + // 返回 + return gcd; + } + + /** + * @param a + * 数字 + * @param b + * 数字 + * @return 两个数的最小公倍数 lowest common multiple (LCM) + */ + public static int lcm(int a, int b) { + a = Math.round(a); + b = Math.round(b); + return a * b / gcd(a, b); + } + + /** + * @param list + * 一组整数 + * @return 一组整数的最小公倍数 lowest common multiple (LCM) + */ + public static int lcms(int... list) { + // 没数 + if (list.length == 0) + return Integer.MAX_VALUE; + // 一个是自己 + if (list.length == 1) { + return list[0]; + } + // 两个以上 + int lcm = lcm(list[0], list[1]); + for (int i = 2; i < list.length; i++) { + lcm = lcm(lcm, list[i]); + } + // 返回 + return lcm; + } + /** * 计算尺寸 * @@ -41,7 +170,7 @@ public static double dimension(String v, double base) { catch (NumberFormatException e) {} // 百分比 - Pattern p = Pattern.compile("^([0-9.]{1,})%$"); + Pattern p = Regex.getPattern("^([0-9.]{1,})%$"); Matcher m = p.matcher(v); if (m.find()) { Double nb = Double.valueOf(m.group(1)); @@ -210,7 +339,7 @@ public static boolean[] splitBoolean(String str) { boolean[] ns = new boolean[ss.length]; for (int i = 0; i < ns.length; i++) { try { - ns[i] = Pattern.matches("^(1|yes|true|on)$", ss[i].toLowerCase()); + ns[i] = Regex.match("^(1|yes|true|on)$", ss[i].toLowerCase()); } catch (NumberFormatException e) { ns[i] = false; diff --git a/src/org/nutz/lang/Stopwatch.java b/src/org/nutz/lang/Stopwatch.java index 90cb66d453..9ecee40cb9 100644 --- a/src/org/nutz/lang/Stopwatch.java +++ b/src/org/nutz/lang/Stopwatch.java @@ -1,7 +1,7 @@ package org.nutz.lang; -import java.util.ArrayList; import java.util.Date; +import java.util.LinkedList; import java.util.List; /** @@ -145,8 +145,8 @@ public String toString() { String prefix = String.format("Total: %d%s : [%s]=>[%s]", this.getDuration(), (nano ? "ns" : "ms"), - Times.sDTms(new Date(from)), - Times.sDTms(new Date(to))); + Times.sDTms2(new Date(from)), + Times.sDTms2(new Date(to))); if (tags == null) return prefix; StringBuilder sb = new StringBuilder(prefix).append("\r\n"); @@ -163,7 +163,7 @@ public String toString() { public StopTag tag(String name) { if (tags == null) - tags = new ArrayList(); + tags = new LinkedList(); lastTag = new StopTag(name, System.currentTimeMillis(), lastTag); tags.add(lastTag); return lastTag; diff --git a/src/org/nutz/lang/Streams.java b/src/org/nutz/lang/Streams.java index 1fbd7a4f98..f2311219c7 100644 --- a/src/org/nutz/lang/Streams.java +++ b/src/org/nutz/lang/Streams.java @@ -22,6 +22,8 @@ import java.io.Reader; import java.io.Writer; +import org.nutz.lang.stream.FileChannelInputStream; +import org.nutz.lang.stream.FileChannelOutputStream; import org.nutz.lang.stream.VoidInputStream; import org.nutz.resource.NutResource; import org.nutz.resource.Scans; @@ -86,6 +88,7 @@ public static void writeAndClose(Writer writer, CharSequence cs) { throw Lang.wrapThrow(e); } finally { + safeFlush(writer); safeClose(writer); } } @@ -124,15 +127,61 @@ public static long write(OutputStream ops, InputStream ins) throws IOException { * @throws IOException */ public static long write(OutputStream ops, InputStream ins, int bufferSize) throws IOException { + return write(ops, ins, -1, bufferSize); + } + + /** + * 将输入流写入一个输出流。 + *

+ * 注意,它并不会关闭输入/出流 + * + * @param ops + * 输出流 + * @param ins + * 输入流 + * @param limit + * 最多写入多少字节,0 或负数表示不限 + * @param bufferSize + * 缓冲块大小 + * + * @return 写入的字节数 + * + * @throws IOException + */ + public static long write(OutputStream ops, InputStream ins, long limit, int bufferSize) + throws IOException { if (null == ops || null == ins) return 0; byte[] buf = new byte[bufferSize]; int len; long bytesCount = 0; - while (-1 != (len = ins.read(buf))) { - bytesCount += len; - ops.write(buf, 0, len); + if (limit > 0) { + long remain = limit; + while (-1 != (len = ins.read(buf))) { + // 还可以写入的字节数 + if (len > remain) { + len = (int) remain; + remain = 0; + } + // 减去 + else { + remain -= len; + } + bytesCount += len; + ops.write(buf, 0, len); + // 写够了 + if (remain <= 0) { + break; + } + } + } + // 全写 + else { + while (-1 != (len = ins.read(buf))) { + bytesCount += len; + ops.write(buf, 0, len); + } } // 啥都没写,强制触发一下写 // 这是考虑到 walnut 的输出流实现,比如你写一个空文件 @@ -165,6 +214,7 @@ public static long writeAndClose(OutputStream ops, InputStream ins) { throw Lang.wrapThrow(e); } finally { + safeFlush(ops); safeClose(ops); safeClose(ins); } @@ -215,6 +265,7 @@ public static long writeAndClose(Writer writer, Reader reader) { throw Lang.wrapThrow(e); } finally { + safeFlush(writer); safeClose(writer); safeClose(reader); } @@ -255,6 +306,7 @@ public static void writeAndClose(OutputStream ops, byte[] bytes) { throw Lang.wrapThrow(e); } finally { + safeFlush(ops); safeClose(ops); } } @@ -427,6 +479,61 @@ public static BufferedInputStream buff(InputStream ins) { return new BufferedInputStream(ins); } + /** + * 创建采用 nio 方式更快速的文件输入流 + * + * @param f + * 文件对象 + * @return 管道文件数据流 + * + * @throws FileNotFoundException + */ + public static FileChannelInputStream chanIn(File f) throws FileNotFoundException { + return chan(new FileInputStream(f)); + } + + /** + * 包裹采用 nio 方式更快速的文件输入流 + * + * @param ins + * 文件输入流 + * @return 管道文件数据流 + */ + public static FileChannelInputStream chan(FileInputStream ins) { + if (ins == null) + throw new NullPointerException("ins is null!"); + return new FileChannelInputStream(ins); + } + + /** + * 创建采用 nio 方式更快速的文件输出流 + * + * @param f + * 文件对象 + * @param append + * true 为末尾附加模式,false 表示从开头开始写 + * + * @return 管道文件数据流 + * @throws FileNotFoundException + */ + public static FileChannelOutputStream chanOps(File f, boolean append) + throws FileNotFoundException { + return chan(new FileOutputStream(f, append)); + } + + /** + * 包裹采用 nio 方式更快速的文件输出流 + * + * @param ins + * 文件输入流 + * @return 管道文件数据流 + */ + public static FileChannelOutputStream chan(FileOutputStream ops) { + if (ops == null) + throw new NullPointerException("ops is null!"); + return new FileChannelOutputStream(ops); + } + /** * 为一个输出流包裹一个缓冲流。如果这个输出流本身就是缓冲流,则直接返回 * @@ -716,6 +823,7 @@ public static long writeAndClose(OutputStream ops, InputStream ins, int buf) { throw Lang.wrapThrow(e); } finally { + safeFlush(ops); safeClose(ops); safeClose(ins); } diff --git a/src/org/nutz/lang/Strings.java b/src/org/nutz/lang/Strings.java index f8aa49ec1a..a51f0d176f 100644 --- a/src/org/nutz/lang/Strings.java +++ b/src/org/nutz/lang/Strings.java @@ -1559,11 +1559,11 @@ public static boolean isUrl(String s) { } public static Pattern P_CitizenId = Pattern.compile("[1-9]\\d{5}[1-2]\\d{3}((0\\d)|(1[0-2]))(([0|1|2]\\d)|3[0-1])\\d{3}(\\d|X|x)"); - public static Pattern P_Mobile = Pattern.compile("^((13[0-9])|(15[0-9])|(14[0-9])|(17[0-9])|(18[0-9]))\\d{8}$"); + public static Pattern P_Mobile = Pattern.compile("^((13[0-9])|(15[0-9])|(14[0-9])|(16[0-9])|(19[0-9])|(17[0-9])|(18[0-9]))\\d{8}$"); public static Pattern P_ZipCode = Pattern.compile("\\d{6}"); public static Pattern P_Money = Pattern.compile("^(\\d+(?:\\.\\d+)?)$"); public static Pattern P_Number = Pattern.compile("^[\\d]+$"); - public static Pattern P_Email = Pattern.compile("^([a-zA-Z0-9]*[-_]?[\\w.]+)*@([a-zA-Z0-9]*[-_]?[a-zA-Z0-9]+)+[\\\\.][A-Za-z]{2,3}([\\\\.][A-Za-z]{2})?$"); + public static Pattern P_Email = Pattern.compile("^[.\\d\\w_-]+@[\\d\\w_-]+(\\.[\\d\\w]+)+$"); public static Pattern P_QQ = Pattern.compile("[1-9][0-9]{4,10}"); public static Pattern P_USCC = Pattern.compile("^(11|12|13|19|51|52|53|59|91|92|93|Y1)[1-9]{1}[0-9]{5}[0-9A-HJ-NP-RT-UW-Y0-9]{9}[0-90-9A-HJ-NP-RT-UW-Y]{1}$"); public static Pattern P_UnionPayCard = Pattern.compile("^62[0-5]\\d{13,16}$"); @@ -1749,7 +1749,7 @@ public static String removeLast(CharSequence str) { *

* 比如: *

    - *
  • removeLast("12345",5) => "12345" + *
  • removeLast("12345",'5') => "1234" *
  • removeLast("ABC",'B') => "ABC" *
  • removeLast("A",'B') => "A" *
  • removeLast("A",'A') => "" diff --git a/src/org/nutz/lang/Times.java b/src/org/nutz/lang/Times.java index ea392870d8..ababc09995 100644 --- a/src/org/nutz/lang/Times.java +++ b/src/org/nutz/lang/Times.java @@ -1,1636 +1,1670 @@ -package org.nutz.lang; - -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * 一些时间相关的帮助函数 - * - * @author zozoh(zozohtnt@gmail.com) - */ -public abstract class Times { - - /** - * 判断一年是否为闰年,如果给定年份小于1全部为 false - * - * @param year - * 年份,比如 2012 就是二零一二年 - * @return 给定年份是否是闰年 - */ - public static boolean leapYear(int year) { - if (year < 4) - return false; - return (year % 400 == 0) || (year % 100 != 0 && year % 4 == 0); - } - - /** - * 判断某年(不包括自己)之前有多少个闰年 - * - * @param year - * 年份,比如 2012 就是二零一二年 - * @return 闰年的个数 - */ - public static int countLeapYear(int year) { - // 因为要计算年份到公元元年(0001年)的年份跨度,所以减去1 - int span = year - 1; - return (span / 4) - (span / 100) + (span / 400); - } - - /** - * 将一个秒数(天中),转换成一个如下格式的数组: - * - *
    -     * [0-23][0-59[-059]
    -     * 
    - * - * @param sec - * 秒数 - * @return 时分秒的数组 - */ - public static int[] T(int sec) { - TmInfo ti = Ti(sec); - return Nums.array(ti.hour, ti.minute, ti.second); - } - - /** - * 将一个时间字符串,转换成一个一天中的绝对秒数 - * - * @param ts - * 时间字符串,符合格式 "HH:mm:ss" 或者 "HH:mm" - * @return 一天中的绝对秒数 - */ - public static int T(String ts) { - return Ti(ts).value; - } - - /** - * 将一个秒数(天中),转换成一个时间对象: - * - * @param sec - * 秒数 - * @return 时间对象 - */ - public static TmInfo Ti(int sec) { - TmInfo ti = new TmInfo(); - ti.valueInMillisecond = (int) sec * 1000; - ti.__recound_by_valueInMilliSecond(); - return ti; - } - - /** - * 将一个毫秒数(天中),转换成一个时间对象: - * - * @param ams - * 毫秒数 - * @return 时间对象 - */ - public static TmInfo Tims(long ams) { - TmInfo ti = new TmInfo(); - ti.valueInMillisecond = (int) ams; - ti.__recound_by_valueInMilliSecond(); - return ti; - } - - private static final Pattern _p_tm = Pattern.compile("^([0-9]{1,2}):([0-9]{1,2})(:([0-9]{1,2})([.,]([0-9]{1,3}))?)?$"); - - /** - * 将一个时间字符串,转换成一个一天中的绝对时间对象 - * - * @param ts - * 时间字符串,符合格式 - *
      - *
    • "HH:mm:ss" - *
    • "HH:mm" - *
    • "HH:mm:ss.SSS" - *
    • "HH:mm:ss,SSS" - *
    - * @return 时间对象 - */ - public static TmInfo Ti(String ts) { - Matcher m = _p_tm.matcher(ts); - - if (m.find()) { - TmInfo ti = new TmInfo(); - // 仅仅到分钟 - if (null == m.group(3)) { - ti.hour = Integer.parseInt(m.group(1)); - ti.minute = Integer.parseInt(m.group(2)); - ti.second = 0; - ti.millisecond = 0; - } - // 到秒 - else if (null == m.group(5)) { - ti.hour = Integer.parseInt(m.group(1)); - ti.minute = Integer.parseInt(m.group(2)); - ti.second = Integer.parseInt(m.group(4)); - ti.millisecond = 0; - } - // 到毫秒 - else { - ti.hour = Integer.parseInt(m.group(1)); - ti.minute = Integer.parseInt(m.group(2)); - ti.second = Integer.parseInt(m.group(4)); - ti.millisecond = Integer.parseInt(m.group(6)); - } - // 计算其他的值 - ti.value = ti.hour * 3600 + ti.minute * 60 + ti.second; - ti.valueInMillisecond = ti.value * 1000 + ti.millisecond; - // 返回 - return ti; - } - throw Lang.makeThrow("Wrong format of time string '%s'", ts); - } - - /** - * 描述了一个时间(一天内)的结构信息 - */ - public static class TmInfo { - public int value; - public int valueInMillisecond; - public int hour; - public int minute; - public int second; - public int millisecond; - - public void offset(int sec) { - this.valueInMillisecond += sec * 1000; - this.__recound_by_valueInMilliSecond(); - } - - public void offsetInMillisecond(int ms) { - this.valueInMillisecond += ms; - this.__recound_by_valueInMilliSecond(); - } - - private void __recound_by_valueInMilliSecond() { - // 确保毫秒数在一天之内,即 [0, 86399000] - if (this.valueInMillisecond >= 86400000) { - this.valueInMillisecond = this.valueInMillisecond % 86400000; - } - // 负数表示后退 - else if (this.valueInMillisecond < 0) { - this.valueInMillisecond = this.valueInMillisecond % 86400000; - if (this.valueInMillisecond < 0) - this.valueInMillisecond = 86400000 + this.valueInMillisecond; - } - // 计算其他值 - this.value = this.valueInMillisecond / 1000; - this.millisecond = this.valueInMillisecond - this.value * 1000; - this.hour = Math.min(23, this.value / 3600); - this.minute = Math.min(59, (this.value - (this.hour * 3600)) / 60); - this.second = Math.min(59, this.value - (this.hour * 3600) - (this.minute * 60)); - } - - public String toString() { - String fmt = "HH:mm"; - // 到毫秒 - if (0 != this.millisecond) { - fmt += ":ss.SSS"; - } - // 到秒 - else if (0 != this.second) { - fmt += ":ss"; - } - return toString(fmt); - } - - private static Pattern _p_tmfmt = Pattern.compile("a|[HhKkms]{1,2}|S(SS)?"); - - /** - *
    -         * a    Am/pm marker (AM/PM)
    -         * H   Hour in day (0-23)
    -         * k   Hour in day (1-24)
    -         * K   Hour in am/pm (0-11)
    -         * h   Hour in am/pm (1-12)
    -         * m   Minute in hour
    -         * s   Second in minute
    -         * S   Millisecond Number
    -         * HH  补零的小时(0-23)
    -         * kk  补零的小时(1-24)
    -         * KK  补零的半天小时(0-11)
    -         * hh  补零的半天小时(1-12)
    -         * mm  补零的分钟
    -         * ss  补零的秒
    -         * SSS 补零的毫秒
    -         * 
    - * - * @param fmt - * 格式化字符串类似 "HH:mm:ss,SSS" - * @return 格式化后的时间 - */ - public String toString(String fmt) { - StringBuilder sb = new StringBuilder(); - fmt = Strings.sBlank(fmt, "HH:mm:ss"); - Matcher m = _p_tmfmt.matcher(fmt); - int pos = 0; - while (m.find()) { - int l = m.start(); - // 记录之前 - if (l > pos) { - sb.append(fmt.substring(pos, l)); - } - // 偏移 - pos = m.end(); - - // 替换 - String s = m.group(0); - if ("a".equals(s)) { - sb.append(this.value > 43200 ? "PM" : "AM"); - } - // H Hour in day (0-23) - else if ("H".equals(s)) { - sb.append(this.hour); - } - // k Hour in day (1-24) - else if ("k".equals(s)) { - sb.append(this.hour + 1); - } - // K Hour in am/pm (0-11) - else if ("K".equals(s)) { - sb.append(this.hour % 12); - } - // h Hour in am/pm (1-12) - else if ("h".equals(s)) { - sb.append((this.hour % 12) + 1); - } - // m Minute in hour - else if ("m".equals(s)) { - sb.append(this.minute); - } - // s Second in minute - else if ("s".equals(s)) { - sb.append(this.second); - } - // S Millisecond Number - else if ("S".equals(s)) { - sb.append(this.millisecond); - } - // HH 补零的小时(0-23) - else if ("HH".equals(s)) { - sb.append(String.format("%02d", this.hour)); - } - // kk 补零的小时(1-24) - else if ("kk".equals(s)) { - sb.append(String.format("%02d", this.hour + 1)); - } - // KK 补零的半天小时(0-11) - else if ("KK".equals(s)) { - sb.append(String.format("%02d", this.hour % 12)); - } - // hh 补零的半天小时(1-12) - else if ("hh".equals(s)) { - sb.append(String.format("%02d", (this.hour % 12) + 1)); - } - // mm 补零的分钟 - else if ("mm".equals(s)) { - sb.append(String.format("%02d", this.minute)); - } - // ss 补零的秒 - else if ("ss".equals(s)) { - sb.append(String.format("%02d", this.second)); - } - // SSS 补零的毫秒 - else if ("SSS".equals(s)) { - sb.append(String.format("%03d", this.millisecond)); - } - // 不认识 - else { - sb.append(s); - } - } - // 结尾 - if (pos < fmt.length()) { - sb.append(fmt.substring(pos)); - } - - // 返回 - return sb.toString(); - } - } - - /** - * 返回服务器当前时间 - * - * @return 服务器当前时间 - */ - public static Date now() { - return new Date(System.currentTimeMillis()); - } - - private static Pattern _P_TIME = Pattern.compile("^((\\d{2,4})([/\\\\-])?(\\d{1,2})([/\\\\-])?(\\d{1,2}))?" - + "(([ T])?" - + "(\\d{1,2})(:)(\\d{1,2})((:)(\\d{1,2}))?" - + "(([.])" - + "(\\d{1,}))?)?" - + "(([+-])(\\d{1,2})(:\\d{1,2})?)?" - + "$"); - - private static Pattern _P_TIME_LONG = Pattern.compile("^[0-9]+(L)?$"); - - /** - * 根据默认时区计算时间字符串的绝对毫秒数 - * - * @param ds - * 时间字符串 - * @return 绝对毫秒数 - * - * @see #ams(String, TimeZone) - */ - public static long ams(String ds) { - return ams(ds, null); - } - - /** - * 根据字符串得到相对于 "UTC 1970-01-01 00:00:00" 的绝对毫秒数。 - * 本函数假想给定的时间字符串是本地时间。所以计算出来结果后,还需要减去时差 - * - * 支持的时间格式字符串为: - * - *
    -     * yyyy-MM-dd HH:mm:ss
    -     * yyyy-MM-dd HH:mm:ss.SSS
    -     * yy-MM-dd HH:mm:ss;
    -     * yy-MM-dd HH:mm:ss.SSS;
    -     * yyyy-MM-dd;
    -     * yy-MM-dd;
    -     * HH:mm:ss;
    -     * HH:mm:ss.SSS;
    -     * 
    - * - * 时间字符串后面可以跟 +8 或者 +8:00 表示 GMT+8:00 时区。 同理 -9 或者 -9:00 表示 GMT-9:00 时区 - * - * @param ds - * 时间字符串 - * @param tz - * 你给定的时间字符串是属于哪个时区的 - * @return 时间 - * @see #_P_TIME - */ - public static long ams(String ds, TimeZone tz) { - Matcher m = _P_TIME.matcher(ds); - if (m.find()) { - int yy = _int(m, 2, 1970); - int MM = _int(m, 4, 1); - int dd = _int(m, 6, 1); - - int HH = _int(m, 9, 0); - int mm = _int(m, 11, 0); - int ss = _int(m, 14, 0); - - int ms = _int(m, 17, 0); - - /* - * zozoh: 先干掉,还是用 SimpleDateFormat 吧,"1980-05-01 15:17:23" 之前的日子 - * 得出的时间竟然总是多 30 分钟 long day = (long) D1970(yy, MM, dd); long MS = - * day * 86400000L; MS += (((long) HH) * 3600L + ((long) mm) * 60L + - * ss) * 1000L; MS += (long) ms; - * - * // 如果没有指定时区 ... if (null == tz) { // 那么用字符串中带有的时区信息, if - * (!Strings.isBlank(m.group(17))) { tz = - * TimeZone.getTimeZone(String.format("GMT%s%s:00", m.group(18), - * m.group(19))); // tzOffset = Long.parseLong(m.group(19)) // * - * 3600000L // * (m.group(18).charAt(0) == '-' ? -1 : 1); - * - * } // 如果依然木有,则用系统默认时区 else { tz = TimeZone.getDefault(); } } - * - * // 计算 return MS - tz.getRawOffset() - tz.getDSTSavings(); - */ - String str = String.format("%04d-%02d-%02d %02d:%02d:%02d.%03d", - yy, - MM, - dd, - HH, - mm, - ss, - ms); - SimpleDateFormat df = (SimpleDateFormat) DF_DATE_TIME_MS4.clone(); - // 那么用字符串中带有的时区信息 ... - if (null == tz && !Strings.isBlank(m.group(18))) { - tz = TimeZone.getTimeZone(String.format("GMT%s%s:00", m.group(19), m.group(20))); - } - // 指定时区 ... - if (null != tz) - df.setTimeZone(tz); - // 解析返回 - try { - return df.parse(str).getTime(); - } - catch (ParseException e) { - throw Lang.wrapThrow(e); - } - } else if (_P_TIME_LONG.matcher(ds).find()) { - if (ds.endsWith("L")) - ds.substring(0, ds.length() - 1); - return Long.parseLong(ds); - } - throw Lang.makeThrow("Unexpect date format '%s'", ds); - } - - /** - * 这个接口函数是 1.b.49 提供了,下一版本将改名为 ams,预计在版本 1.b.51 之后被移除 - * - * @deprecated since 1.b.49 util 1.b.51 - */ - public static long ms(String ds, TimeZone tz) { - return ams(ds, tz); - } - - /** - * 返回时间对象在一天中的毫秒数 - * - * @param d - * 时间对象 - * - * @return 时间对象在一天中的毫秒数 - */ - public static long ms(Date d) { - return ms(C(d)); - } - - /** - * 返回时间对象在一天中的毫秒数 - * - * @param c - * 时间对象 - * - * @return 时间对象在一天中的毫秒数 - */ - public static int ms(Calendar c) { - int ms = c.get(Calendar.HOUR_OF_DAY) * 3600000; - ms += c.get(Calendar.MINUTE) * 60000; - ms += c.get(Calendar.SECOND) * 1000; - ms += c.get(Calendar.MILLISECOND); - return ms; - } - - /** - * 返回当前时间在一天中的毫秒数 - * - * @return 当前时间在一天中的毫秒数 - */ - public static int ms() { - return ms(Calendar.getInstance()); - } - - /** - * 返回当前时间在一天中的毫秒数 - * - * @param str - * 时间字符串 - * - * @return 当前时间在一天中的毫秒数 - */ - public static int ms(String str) { - return Ti(str).valueInMillisecond; - } - - /** - * 根据一个当天的绝对毫秒数,得到一个时间字符串,格式为 "HH:mm:ss.EEE" - * - * @param ms - * 当天的绝对毫秒数 - * @return 时间字符串 - */ - public static String mss(int ms) { - int sec = ms / 1000; - ms = ms - sec * 1000; - return secs((int) sec) + "." + Strings.alignRight(ms, 3, '0'); - } - - /** - * 根据一个当天的绝对秒数,得到一个时间字符串,格式为 "HH:mm:ss" - * - * @param sec - * 当天的绝对秒数 - * @return 时间字符串 - */ - public static String secs(int sec) { - int hh = sec / 3600; - sec -= hh * 3600; - int mm = sec / 60; - sec -= mm * 60; - return Strings.alignRight(hh, 2, '0') - + ":" - + Strings.alignRight(mm, 2, '0') - + ":" - + Strings.alignRight(sec, 2, '0'); - - } - - /** - * 返回时间对象在一天中的秒数 - * - * @param d - * 时间对象 - * - * @return 时间对象在一天中的秒数 - */ - public static int sec(Date d) { - Calendar c = C(d); - int sec = c.get(Calendar.HOUR_OF_DAY) * 3600; - sec += c.get(Calendar.MINUTE) * 60; - sec += c.get(Calendar.SECOND); - return sec; - } - - /** - * 返回当前时间在一天中的秒数 - * - * @return 当前时间在一天中的秒数 - */ - public static int sec() { - return sec(now()); - } - - /** - * 根据字符串得到时间对象 - * - * @param ds - * 时间字符串 - * @return 时间 - * - * @see #ams(String) - */ - public static Date D(String ds) { - return D(ams(ds)); - } - - private static int _int(Matcher m, int index, int dft) { - String s = m.group(index); - if (Strings.isBlank(s)) - return dft; - return Integer.parseInt(s); - } - - // 常量数组,一年每个月多少天 - private static final int[] _MDs = new int[]{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - - /** - * 计算一个给定日期,距离 1970 年 1 月 1 日有多少天 - * - * @param yy - * 年,比如 1999,或者 43 - * @param MM - * 月,一月为 1,十二月为 12 - * @param dd - * 日,每月一号为 1 - * @return 距离 1970 年 1 月 1 日的天数 - */ - public static int D1970(int yy, int MM, int dd) { - // 转换成相对公元元年的年份 - // 如果给的年份小于 100,那么就认为是从 1970 开始算的年份 - int year = (yy < 100 ? yy + 1970 : yy); - // 得到今年之前的基本天数 - int day = (year - 1970) * 365; - // 补上闰年天数 - day += countLeapYear(year) - countLeapYear(1970); - // 计算今年本月之前的月份 - int mi = Math.min(MM - 1, 11); - boolean isLeapYear = leapYear(yy); - for (int i = 0; i < mi; i++) { - day += _MDs[i]; - } - // 考虑今年是闰年的情况 - if (isLeapYear && MM > 2) { - day++; - } - // 最后加上天数 - day += Math.min(dd, _MDs[mi]) - 1; - - // 如果是闰年且本月是 2 月 - if (isLeapYear && dd == 29) { - day++; - } - - // 如果是闰年并且过了二月 - return day; - } - - /** - * 根据毫秒数得到时间 - * - * @param ms - * 时间的毫秒数 - * @return 时间 - */ - public static Date D(long ms) { - return new Date(ms); - } - - /** - * 根据字符串得到时间 - * - *
    -     * 如果你输入了格式为 "yyyy-MM-dd HH:mm:ss"
    -     *    那么会匹配到秒
    -     *    
    -     * 如果你输入格式为 "yyyy-MM-dd"
    -     *    相当于你输入了 "yyyy-MM-dd 00:00:00"
    -     * 
    - * - * @param ds - * 时间字符串 - * @return 时间 - */ - public static Calendar C(String ds) { - return C(D(ds)); - } - - /** - * 根据日期对象得到时间 - * - * @param d - * 时间对象 - * @return 时间 - */ - public static Calendar C(Date d) { - return C(d.getTime()); - } - - /** - * 根据毫秒数得到时间 - * - * @param ms - * 时间的毫秒数 - * @return 时间 - */ - public static Calendar C(long ms) { - Calendar c = Calendar.getInstance(); - c.setTimeInMillis(ms); - return c; - } - - /** - * 把时间转换成格式为 y-M-d H:m:s.S 的字符串 - * - * @param d - * 时间对象 - * @return 该时间的字符串形式 , 格式为 y-M-d H:m:s.S - */ - public static String sDTms(Date d) { - return format(DF_DATE_TIME_MS, d); - } - - /** - * 把时间转换成格式为 yy-MM-dd HH:mm:ss.SSS 的字符串 - * - * @param d - * 时间对象 - * @return 该时间的字符串形式 , 格式为 yy-MM-dd HH:mm:ss.SSS - */ - public static String sDTms2(Date d) { - return format(DF_DATE_TIME_MS2, d); - } - - /** - * 把时间转换成格式为 yyyy-MM-dd HH:mm:ss 的字符串 - * - * @param d - * 时间对象 - * @return 该时间的字符串形式 , 格式为 yyyy-MM-dd HH:mm:ss - */ - public static String sDT(Date d) { - return format(DF_DATE_TIME, d); - } - - /** - * 把时间转换成格式为 yyyy-MM-dd 的字符串 - * - * @param d - * 时间对象 - * @return 该时间的字符串形式 , 格式为 yyyy-MM-dd - */ - public static String sD(Date d) { - return format(DF_DATE, d); - } - - /** - * 将一个秒数(天中),转换成一个格式为 HH:mm:ss 的字符串 - * - * @param sec - * 秒数 - * @return 格式为 HH:mm:ss 的字符串 - */ - public static String sT(int sec) { - int[] ss = T(sec); - return Strings.alignRight(ss[0], 2, '0') - + ":" - + Strings.alignRight(ss[1], 2, '0') - + ":" - + Strings.alignRight(ss[2], 2, '0'); - } - - /** - * 将一个秒数(天中),转换成一个格式为 HH:mm 的字符串(精确到分钟) - * - * @param sec - * 秒数 - * @return 格式为 HH:mm 的字符串 - */ - public static String sTmin(int sec) { - int[] ss = T(sec); - return Strings.alignRight(ss[0], 2, '0') + ":" + Strings.alignRight(ss[1], 2, '0'); - } - - /** - * 将一个毫秒秒数(天中),转换成一个格式为 HH:mm:ss,SSS 的字符串(精确到毫秒) - * - * @param ams - * 当天毫秒数 - * @return 格式为 HH:mm:ss,SSS 的字符串 - */ - public static String sTms(long ams) { - return Tims(ams).toString("HH:mm:ss,SSS"); - } - - /** - * 以本周为基础获得某一周的时间范围 - * - * @param off - * 从本周偏移几周, 0 表示本周,-1 表示上一周,1 表示下一周 - * - * @return 时间范围(毫秒级别) - * - * @see org.nutz.lang.Times#weeks(long, int, int) - */ - public static Date[] week(int off) { - return week(System.currentTimeMillis(), off); - } - - /** - * 以某周为基础获得某一周的时间范围 - * - * @param base - * 基础时间,毫秒 - * @param off - * 从本周偏移几周, 0 表示本周,-1 表示上一周,1 表示下一周 - * - * @return 时间范围(毫秒级别) - * - * @see org.nutz.lang.Times#weeks(long, int, int) - */ - public static Date[] week(long base, int off) { - return weeks(base, off, off); - } - - /** - * 以本周为基础获得时间范围 - * - * @param offL - * 从本周偏移几周, 0 表示本周,-1 表示上一周,1 表示下一周 - * @param offR - * 从本周偏移几周, 0 表示本周,-1 表示上一周,1 表示下一周 - * - * @return 时间范围(毫秒级别) - * - * @see org.nutz.lang.Times#weeks(long, int, int) - */ - public static Date[] weeks(int offL, int offR) { - return weeks(System.currentTimeMillis(), offL, offR); - } - - /** - * 按周获得某几周周一 00:00:00 到周六 的时间范围 - *

    - * 它会根据给定的 offL 和 offR 得到一个时间范围 - *

    - * 对本函数来说 week(-3,-5) 和 week(-5,-3) 是一个意思 - * - * @param base - * 基础时间,毫秒 - * @param offL - * 从本周偏移几周, 0 表示本周,-1 表示上一周,1 表示下一周 - * @param offR - * 从本周偏移几周, 0 表示本周,-1 表示上一周,1 表示下一周 - * - * @return 时间范围(毫秒级别) - */ - public static Date[] weeks(long base, int offL, int offR) { - int from = Math.min(offL, offR); - int len = Math.abs(offL - offR); - // 现在 - Calendar c = Calendar.getInstance(); - c.setTimeInMillis(base); - - Date[] re = new Date[2]; - - // 计算开始 - c.add(Calendar.DAY_OF_YEAR, 7 * from); - c.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY); - c.set(Calendar.HOUR_OF_DAY, 0); - c.set(Calendar.MINUTE, 0); - c.set(Calendar.SECOND, 0); - c.set(Calendar.MILLISECOND, 0); - re[0] = c.getTime(); - - // 计算结束 - c.add(Calendar.DAY_OF_YEAR, 7 * (len + 1)); - c.add(Calendar.MILLISECOND, -1); - re[1] = c.getTime(); - - // 返回 - return re; - } - - private static final String[] _MMM = new String[]{"Jan", - "Feb", - "Mar", - "Apr", - "May", - "Jun", - "Jul", - "Aug", - "Sep", - "Oct", - "Nov", - "Dec"}; - - /** - * 将一个时间格式化成容易被人类阅读的格式 - * - *

    -     * 如果 1 分钟内,打印 Just Now
    -     * 如果 1 小时内,打印多少分钟
    -     * 如果 1 天之内,打印多少小时之前
    -     * 如果是今年之内,打印月份和日期
    -     * 否则打印月份和年
    -     * 
    - * - * @param d - * @return 日期字符串 - */ - public static String formatForRead(Date d) { - long ms = System.currentTimeMillis() - d.getTime(); - // 如果 1 分钟内,打印 Just Now - if (ms < (60000)) { - return "Just Now"; - } - // 如果 1 小时内,打印多少分钟 - if (ms < (60 * 60000)) { - return "" + (ms / 60000) + "Min."; - } - - // 如果 1 天之内,打印多少小时之前 - if (ms < (24 * 3600 * 1000)) { - return "" + (ms / 3600000) + "hr."; - } - - // 如果一周之内,打印多少天之前 - if (ms < (7 * 24 * 3600 * 1000)) { - return "" + (ms / (24 * 3600000)) + "Day"; - } - - // 如果是今年之内,打印月份和日期 - Calendar c = Calendar.getInstance(); - int thisYear = c.get(Calendar.YEAR); - - c.setTime(d); - int yy = c.get(Calendar.YEAR); - int mm = c.get(Calendar.MONTH); - if (thisYear == yy) { - int dd = c.get(Calendar.DAY_OF_MONTH); - return String.format("%s %d", _MMM[mm], dd); - } - - // 否则打印月份和年 - return String.format("%s %d", _MMM[mm], yy); - } - - /** - * 以给定的时间格式来安全的对时间进行格式化,并返回格式化后所对应的字符串 - * - * @param fmt - * 时间格式 - * @param d - * 时间对象 - * @return 格式化后的字符串 - */ - public static String format(DateFormat fmt, Date d) { - return ((DateFormat) fmt.clone()).format(d); - } - - /** - * 以给定的时间格式来安全的对时间进行格式化,并返回格式化后所对应的字符串 - * - * @param fmt - * 时间格式 - * @param d - * 时间对象 - * @return 格式化后的字符串 - */ - public static String format(String fmt, Date d) { - return new SimpleDateFormat(fmt, Locale.ENGLISH).format(d); - } - - /** - * 以给定的时间格式来安全的解析时间字符串,并返回解析后所对应的时间对象(包裹RuntimeException) - * - * @param fmt - * 时间格式 - * @param s - * 时间字符串 - * @return 该时间字符串对应的时间对象 - */ - public static Date parseq(DateFormat fmt, String s) { - try { - return parse(fmt, s); - } - catch (ParseException e) { - throw Lang.wrapThrow(e); - } - } - - /** - * 以给定的时间格式来安全的解析时间字符串,并返回解析后所对应的时间对象(包裹RuntimeException) - * - * @param fmt - * 时间格式 - * @param s - * 时间字符串 - * @return 该时间字符串对应的时间对象 - */ - public static Date parseq(String fmt, String s) { - try { - return parse(fmt, s); - } - catch (ParseException e) { - throw Lang.wrapThrow(e); - } - } - - /** - * 以给定的时间格式来安全的解析时间字符串,并返回解析后所对应的时间对象 - * - * @param fmt - * 时间格式 - * @param s - * 日期时间字符串 - * @return 该时间字符串对应的时间对象 - */ - public static Date parse(DateFormat fmt, String s) throws ParseException { - return ((DateFormat) fmt.clone()).parse(s); - } - - /** - * 以给定的时间格式来安全的解析时间字符串,并返回解析后所对应的时间对象 - * - * @param fmt - * 时间格式 - * @param s - * 日期时间字符串 - * @return 该时间字符串对应的时间对象 - */ - public static Date parse(String fmt, String s) throws ParseException { - return new SimpleDateFormat(fmt).parse(s); - } - - private static final DateFormat DF_DATE_TIME_MS = new SimpleDateFormat("y-M-d H:m:s.S"); - private static final DateFormat DF_DATE_TIME_MS2 = new SimpleDateFormat("yy-MM-dd HH:mm:ss.SSS"); - private static final DateFormat DF_DATE_TIME_MS4 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); - private static final DateFormat DF_DATE_TIME = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - private static final DateFormat DF_DATE = new SimpleDateFormat("yyyy-MM-dd"); - // private static final DateFormat DF_MONTH = new - // SimpleDateFormat("yyyy-MM"); - - public static final long T_1S = 1000; - public static final long T_1M = 60 * 1000; - public static final long T_1H = 60 * 60 * 1000; - public static final long T_1D = 24 * 60 * 60 * 1000; - - /** - * 方便的把时间换算成毫秒数 - * - * 支持几个单位, s(秒), m(分钟), h(小时), d(天) - * - * 比如: - * - * 100s -> 100000
    - * 2m -> 120000
    - * 3h -> 10800000
    - * - * @param tstr - * 时间字符串 - * @return 毫秒数 - */ - public static long toMillis(String tstr) { - if (Strings.isBlank(tstr)) { - return 0; - } - tstr = tstr.toLowerCase(); - // FIXME 稍后改成正则判断 - String tl = tstr.substring(0, tstr.length() - 1); - String tu = tstr.substring(tstr.length() - 1); - if (TIME_S_EN.equals(tu)) { - return T_1S * Long.valueOf(tl); - } - if (TIME_M_EN.equals(tu)) { - return T_1M * Long.valueOf(tl); - } - if (TIME_H_EN.equals(tu)) { - return T_1H * Long.valueOf(tl); - } - if (TIME_D_EN.equals(tu)) { - return T_1D * Long.valueOf(tl); - } - return Long.valueOf(tstr); - } - - private static String TIME_S_EN = "s"; - private static String TIME_M_EN = "m"; - private static String TIME_H_EN = "h"; - private static String TIME_D_EN = "d"; - - private static String TIME_S_CN = "秒"; - private static String TIME_M_CN = "分"; - private static String TIME_H_CN = "时"; - private static String TIME_D_CN = "天"; - - /** - * 一段时间长度的毫秒数转换为一个时间长度的字符串 - * - * 1000 -> 1S - * - * 120000 - 2M - * - * @param mi - * 毫秒数 - * @return 可读的文字 - */ - public static String fromMillis(long mi) { - return _fromMillis(mi, true); - } - - /** - * fromMillis的中文版本 - * - * 1000 -> 1秒 - * - * 120000 - 2分 - * - * @param mi - * 毫秒数 - * @return 可读的文字 - */ - public static String fromMillisCN(long mi) { - return _fromMillis(mi, false); - } - - private static String _fromMillis(long mi, boolean useEnglish) { - if (mi <= T_1S) { - return "1" + (useEnglish ? TIME_S_EN : TIME_S_CN); - } - if (mi < T_1M && mi > T_1S) { - return (int) (mi / T_1S) + (useEnglish ? TIME_S_EN : TIME_S_CN); - } - if (mi >= T_1M && mi < T_1H) { - int m = (int) (mi / T_1M); - return m - + (useEnglish ? TIME_M_EN : TIME_M_CN) - + _fromMillis(mi - m * T_1M, useEnglish); - } - if (mi >= T_1H && mi < T_1D) { - int h = (int) (mi / T_1H); - return h - + (useEnglish ? TIME_H_EN : TIME_H_CN) - + _fromMillis(mi - h * T_1H, useEnglish); - } - // if (mi >= T_1D) { - int d = (int) (mi / T_1D); - return d + (useEnglish ? TIME_D_EN : TIME_D_CN) + _fromMillis(mi - d * T_1D, useEnglish); - // } - // WTF ? - // throw Lang.impossible(); - } - - /** - * 比较2个字符串格式时间yyyy-MM-dd hh:mm:ss大小 2017-2-8 17:14:14 - * - * @param t1 - * 第一个时间 - * @param t2 - * 第二个时间 - * @return true,如果相等 - */ - public static boolean sDTcompare(String t1, String t2) { - // 将字符串形式的时间转化为Date类型的时间 - Date d1 = parseq(DF_DATE_TIME, t1); - Date d2 = parseq(DF_DATE_TIME, t2); - // Date类的一个方法,如果a早于b返回true,否则返回false - if (d1.before(d2)) - return true; - else - return false; - } - - /** - * Unix时间戳转String日期 - * - * @param timestamp - * 时间戳 - * @param sf - * 日期格式 - * @return 日期字符串 - */ - public static String ts2S(long timestamp, String sf) { - DateFormat format = new SimpleDateFormat(sf); - return format.format(new Date(Long.parseLong(timestamp * 1000 + ""))); - } - - /** - * 取Unix时间戳 - * - * @return 时间戳 - */ - public static long getTS() { - return System.currentTimeMillis() / 1000; - } - - /** - * 字符串yyyy-MM-dd HH:mm:ss时间转化成Unix时间戳 - * - * @param str - * 日期,符合yyyy-MM-dd HH:mm:ss - * @return timestamp 时间戳字符串 - */ - public static String sDT2TS(String str, DateFormat df) { - String timestamp = null; - Date date; - try { - date = df.parse(str); - long l = date.getTime(); - String tmp = String.valueOf(l); - timestamp = tmp.substring(0, 10); - } - catch (Exception e) { - e.printStackTrace(); - } - return timestamp; - } - - /** - * 取当前时间的字符串形式 , 格式为 yyyy-MM-dd HH:mm:ss - * - * @return 时间字符串 - */ - public static String getNowSDT() { - return sDT(now()); - } - - /** - * 获得某月的天数 - * - * @param year - * 年 - * @param month - * 月 - * @return int 指定年月的天数 - */ - public static int getDaysOfMonth(String year, String month) { - int days = 0; - if (month.equals("1") - || month.equals("3") - || month.equals("5") - || month.equals("7") - || month.equals("8") - || month.equals("10") - || month.equals("12")) { - days = 31; - } else if (month.equals("4") - || month.equals("6") - || month.equals("9") - || month.equals("11")) { - days = 30; - } else { - if ((Integer.parseInt(year) % 4 == 0 && Integer.parseInt(year) % 100 != 0) - || Integer.parseInt(year) % 400 == 0) { - days = 29; - } else { - days = 28; - } - } - return days; - } - - /** - * 获取某年某月的天数 - * - * @param year - * int 年 - * @param month - * int 月份[1-12] 月 - * @return int 指定年月的天数 - */ - public static int getDaysOfMonth(int year, int month) { - Calendar calendar = Calendar.getInstance(); - calendar.set(year, month - 1, 1); - return calendar.getActualMaximum(Calendar.DAY_OF_MONTH); - } - - /** - * 获得当前日期 - * - * @return 当前日期,按月算,即DAY_OF_MONTH - */ - public static int getToday() { - Calendar calendar = Calendar.getInstance(); - return calendar.get(Calendar.DATE); - } - - /** - * 获得当前月份 - * - * @return 当前月份,1开始算 - */ - public static int getToMonth() { - Calendar calendar = Calendar.getInstance(); - return calendar.get(Calendar.MONTH) + 1; - } - - /** - * 获得当前年份 - * - * @return 当前年份 - */ - public static int getToYear() { - Calendar calendar = Calendar.getInstance(); - return calendar.get(Calendar.YEAR); - } - - /** - * 返回日期的天 - * - * @param date - * 指定的Date - * @return 指定时间所在月的DAY_OF_MONTH - */ - public static int getDay(Date date) { - Calendar calendar = Calendar.getInstance(); - calendar.setTime(date); - return calendar.get(Calendar.DATE); - } - - /** - * 返回日期的年 - * - * @param date - * 指定的Date - * @return 指定时间的年份 - */ - public static int getYear(Date date) { - Calendar calendar = Calendar.getInstance(); - calendar.setTime(date); - return calendar.get(Calendar.YEAR); - } - - /** - * 返回日期的月份,1-12 - * - * @param date - * 指定的Date - * @return 指定时间的月份 - */ - public static int getMonth(Date date) { - Calendar calendar = Calendar.getInstance(); - calendar.setTime(date); - return calendar.get(Calendar.MONTH) + 1; - } - - /** - * 计算两个日期相差的天数,如果date2 > date1 返回正数,否则返回负数 - * - * @param date1 - * Date - * @param date2 - * Date - * @return long - */ - public static long dayDiff(Date date1, Date date2) { - return (date2.getTime() - date1.getTime()) / 86400000; - } - - /** - * 比较两个日期的年差 - * - * @param before - * 前一个日期,格式yyyy-MM-dd - * @param after - * 后一个日期,格式yyyy-MM-dd - * @return 年份差值 - */ - public static int yearDiff(String before, String after) { - Date beforeDay = parseq(DF_DATE, before); - Date afterDay = parseq(DF_DATE, after); - return getYear(afterDay) - getYear(beforeDay); - } - - /** - * 比较指定日期与当前日期的年差 - * - * @param after - * 指定的后一个日期,格式yyyy-MM-dd - * @return 年份差值 - */ - public static int yearDiffCurr(String after) { - Date beforeDay = new Date(); - Date afterDay = parseq(DF_DATE, after); - return getYear(beforeDay) - getYear(afterDay); - } - - /** - * 比较指定日期与当前日期的天差 - * - * @param before - * 指定的前应日期,格式yyyy-MM-dd - * @return 天差 - */ - public static long dayDiffCurr(String before) { - Date currDate = parseq(DF_DATE, sD(now())); - Date beforeDate = parseq(DF_DATE, before); - return (currDate.getTime() - beforeDate.getTime()) / 86400000; - - } - - /** - * 根据生日获取星座 - * - * @param birth - * 日期格式为YYYY-mm-dd - * @return 星座,单一字符 - */ - public static String getAstro(String birth) { - if (!isDate(birth)) { - birth = "2000" + birth; - } - if (!isDate(birth)) { - return ""; - } - int month = Integer.parseInt(birth.substring(birth.indexOf("-") - + 1, - birth.lastIndexOf("-"))); - int day = Integer.parseInt(birth.substring(birth.lastIndexOf("-") + 1)); - String s = "魔羯水瓶双鱼牡羊金牛双子巨蟹狮子处女天秤天蝎射手魔羯"; - int[] arr = {20, 19, 21, 21, 21, 22, 23, 23, 23, 23, 22, 22}; - int start = month * 2 - (day < arr[month - 1] ? 2 : 0); - return s.substring(start, start + 2) + "座"; - } - - /** - * 判断日期是否有效,包括闰年的情况 - * - * @param date - * 日期格式YYYY-mm-dd - * @return true,如果合法 - */ - public static boolean isDate(String date) { - StringBuffer reg = new StringBuffer("^((\\d{2}(([02468][048])|([13579][26]))-?((((0?"); - reg.append("[13578])|(1[02]))-?((0?[1-9])|([1-2][0-9])|(3[01])))"); - reg.append("|(((0?[469])|(11))-?((0?[1-9])|([1-2][0-9])|(30)))|"); - reg.append("(0?2-?((0?[1-9])|([1-2][0-9])))))|(\\d{2}(([02468][12"); - reg.append("35679])|([13579][01345789]))-?((((0?[13578])|(1[02]))"); - reg.append("-?((0?[1-9])|([1-2][0-9])|(3[01])))|(((0?[469])|(11))"); - reg.append("-?((0?[1-9])|([1-2][0-9])|(30)))|(0?2-?((0?["); - reg.append("1-9])|(1[0-9])|(2[0-8]))))))"); - Pattern p = Pattern.compile(reg.toString()); - return p.matcher(date).matches(); - } - - /** - * 取得指定日期过 years 年后的日期 (当 years 为负数表示指定年之前); - * - * @param date - * 日期 为null时表示当天 - * @param years - * 相加(相减)的年数 - */ - public static Date nextYear(Date date, int years) { - Calendar cal = Calendar.getInstance(); - if (date != null) { - cal.setTime(date); - } - cal.add(Calendar.YEAR, years); - return cal.getTime(); - } - - /** - * 取得指定日期过 months 月后的日期 (当 months 为负数表示指定月之前); - * - * @param date - * 日期 为null时表示当天 - * @param months - * 相加(相减)的月数 - */ - public static Date nextMonth(Date date, int months) { - Calendar cal = Calendar.getInstance(); - if (date != null) { - cal.setTime(date); - } - cal.add(Calendar.MONTH, months); - return cal.getTime(); - } - - /** - * 取得指定日期过 day 周后的日期 (当 day 为负数表示指定月之前) - * - * @param date - * 日期 为null时表示当天 - */ - public static Date nextWeek(Date date, int week) { - Calendar cal = Calendar.getInstance(); - if (date != null) { - cal.setTime(date); - } - cal.add(Calendar.WEEK_OF_MONTH, week); - return cal.getTime(); - } - - /** - * 取得指定日期过 day 天后的日期 (当 day 为负数表示指日期之前); - * - * @param date - * 日期 为null时表示当天 - * @param day - * 相加(相减)的月数 - */ - public static Date nextDay(Date date, int day) { - Calendar cal = Calendar.getInstance(); - if (date != null) { - cal.setTime(date); - } - cal.add(Calendar.DAY_OF_YEAR, day); - return cal.getTime(); - } - - /** - * 取得当前时间距离1900/1/1的天数 - * - * @return 天数 - */ - public static int getDayNum() { - int daynum = 0; - GregorianCalendar gd = new GregorianCalendar(); - Date dt = gd.getTime(); - GregorianCalendar gd1 = new GregorianCalendar(1900, 1, 1); - Date dt1 = gd1.getTime(); - daynum = (int) ((dt.getTime() - dt1.getTime()) / (24 * 60 * 60 * 1000)); - return daynum; - } - - /** - * getDayNum的逆方法(用于处理Excel取出的日期格式数据等) - * - * @param day - * 天数 - * @return 反推出的时间 - */ - public static Date getDateByNum(int day) { - GregorianCalendar gd = new GregorianCalendar(1900, 1, 1); - Date date = gd.getTime(); - date = nextDay(date, day); - return date; - } - - /** - * 取得距离今天 day 日的日期 - * - * @param day - * 天数 - * @return 日期字符串 - */ - public static String nextDay(int day) { - Calendar cal = Calendar.getInstance(); - cal.setTime(now()); - cal.add(Calendar.DAY_OF_YEAR, day); - return format(DF_DATE, cal.getTime()); - } - - /** - * 获取明天的日期 - * - * return 明天的日期 - */ - public static String afterDay() { - return nextDay(1); - } - - /** - * 获取昨天的日期 - * - * @return 昨天的日期 - */ - public static String befoDay() { - return nextDay(-1); - } - - /** - * 获取本月最后一天 - * - * @return 本月最后一天 - */ - public static String getLastDayOfMonth() { - Calendar cal = Calendar.getInstance(); - cal.set(Calendar.DATE, 1); - cal.add(Calendar.MONTH, 1); - cal.add(Calendar.DATE, -1); - return format(DF_DATE, cal.getTime()); - } - - /** - * 获取本月第一天 - * - * @return 本月第一天 - */ - public static String getFirstDayOfMonth() { - Calendar cal = Calendar.getInstance(); - cal.set(Calendar.DATE, 1); - return format(DF_DATE, cal.getTime()); - } - - public static final long T_1MS = 1; - public static final long T_1W = 7 * 24 * 60 * 60 * 1000; - - /** - * 判断两个日期相差的时长 - * - * @param s - * 起始日期 - * @param e - * 结束日期 - * @param unit - * 相差的单位 T_1MS 毫秒 T_1S 秒 T_1M 分 T_1H 时 T_1D 天 T_1W 周 - * @return 相差的数量 - */ - public static long between(Date s, Date e, long unit) { - - Date start; - Date end; - if (s.before(e)) { - start = s; - end = e; - } else { - start = e; - end = s; - } - long diff = end.getTime() - start.getTime(); - return diff / unit; - } - - /** - * 取得指定日期过 minute 分钟后的日期 (当 minute 为负数表示指定分钟之前) - * - * @param date - * 日期 为null时表示当天 - */ - public static Date nextMinute(Date date, int minute) { - Calendar cal = Calendar.getInstance(); - if (date != null) { - cal.setTime(date); - } - cal.add(Calendar.MINUTE, minute); - return cal.getTime(); - } - - /** - * 取得指定日期过 second 秒后的日期 (当 second 为负数表示指定秒之前) - * - * @param date - * 日期 为null时表示当天 - */ - public static Date nextSecond(Date date, int second) { - Calendar cal = Calendar.getInstance(); - if (date != null) { - cal.setTime(date); - } - cal.add(Calendar.SECOND, second); - return cal.getTime(); - } - - /** - * 取得指定日期过 hour 小时后的日期 (当 hour 为负数表示指定小时之前) - * - * @param date - * 日期 为null时表示当天 - */ - public static Date nextHour(Date date, int hour) { - Calendar cal = Calendar.getInstance(); - if (date != null) { - cal.setTime(date); - } - cal.add(Calendar.HOUR, hour); - return cal.getTime(); - } - - /** - * Unix时间戳转Date日期 - * - * @param timestamp 时间戳 - * @return 日期 - */ - public static Date ts2D(long timestamp) { - return new Date(Long.parseLong(timestamp * 1000 + "")); - } -} +package org.nutz.lang; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.Locale; +import java.util.TimeZone; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.nutz.lang.util.Regex; + +/** + * 一些时间相关的帮助函数 + * + * @author zozoh(zozohtnt@gmail.com) + */ +public abstract class Times { + + private static final Pattern _p_tm = Pattern.compile("^([0-9]{1,2}):([0-9]{1,2})(:([0-9]{1,2})([.,]([0-9]{1,3}))?)?$"); + + /** + * 判断一年是否为闰年,如果给定年份小于1全部为 false + * + * @param year + * 年份,比如 2012 就是二零一二年 + * @return 给定年份是否是闰年 + */ + public static boolean leapYear(int year) { + if (year < 4) { + return false; + } + return (year % 400 == 0) || (year % 100 != 0 && year % 4 == 0); + } + + /** + * 判断某年(不包括自己)之前有多少个闰年 + * + * @param year + * 年份,比如 2012 就是二零一二年 + * @return 闰年的个数 + */ + public static int countLeapYear(int year) { + // 因为要计算年份到公元元年(0001年)的年份跨度,所以减去1 + int span = year - 1; + return (span / 4) - (span / 100) + (span / 400); + } + + /** + * 将一个秒数(天中),转换成一个如下格式的数组: + * + *
    +     * [0-23][0-59[-059]
    +     * 
    + * + * @param sec + * 秒数 + * @return 时分秒的数组 + */ + public static int[] T(int sec) { + TmInfo ti = Ti(sec); + return Nums.array(ti.hour, ti.minute, ti.second); + } + + /** + * 将一个时间字符串,转换成一个一天中的绝对秒数 + * + * @param ts + * 时间字符串,符合格式 "HH:mm:ss" 或者 "HH:mm" + * @return 一天中的绝对秒数 + */ + public static int T(String ts) { + return Ti(ts).value; + } + + /** + * 将一个秒数(天中),转换成一个时间对象: + * + * @param sec + * 秒数 + * @return 时间对象 + */ + public static TmInfo Ti(int sec) { + TmInfo ti = new TmInfo(); + ti.valueInMillisecond = sec * 1000; + ti.__recound_by_valueInMilliSecond(); + return ti; + } + + /** + * 将一个毫秒数(天中),转换成一个时间对象: + * + * @param ams + * 毫秒数 + * @return 时间对象 + */ + public static TmInfo Tims(long ams) { + TmInfo ti = new TmInfo(); + ti.valueInMillisecond = (int) ams; + ti.__recound_by_valueInMilliSecond(); + return ti; + } + + /** + * 将一个时间字符串,转换成一个一天中的绝对时间对象 + * + * @param ts + * 时间字符串,符合格式 + *
      + *
    • "HH:mm:ss" + *
    • "HH:mm" + *
    • "HH:mm:ss.SSS" + *
    • "HH:mm:ss,SSS" + *
    + * @return 时间对象 + */ + public static TmInfo Ti(String ts) { + Matcher m = _p_tm.matcher(ts); + + if (m.find()) { + TmInfo ti = new TmInfo(); + // 仅仅到分钟 + if (null == m.group(3)) { + ti.hour = Integer.parseInt(m.group(1)); + ti.minute = Integer.parseInt(m.group(2)); + ti.second = 0; + ti.millisecond = 0; + } + // 到秒 + else if (null == m.group(5)) { + ti.hour = Integer.parseInt(m.group(1)); + ti.minute = Integer.parseInt(m.group(2)); + ti.second = Integer.parseInt(m.group(4)); + ti.millisecond = 0; + } + // 到毫秒 + else { + ti.hour = Integer.parseInt(m.group(1)); + ti.minute = Integer.parseInt(m.group(2)); + ti.second = Integer.parseInt(m.group(4)); + ti.millisecond = Integer.parseInt(m.group(6)); + } + // 计算其他的值 + ti.value = ti.hour * 3600 + ti.minute * 60 + ti.second; + ti.valueInMillisecond = ti.value * 1000 + ti.millisecond; + // 返回 + return ti; + } + throw Lang.makeThrow("Wrong format of time string '%s'", ts); + } + + /** + * 描述了一个时间(一天内)的结构信息 + */ + public static class TmInfo { + public int value; + public int valueInMillisecond; + public int hour; + public int minute; + public int second; + public int millisecond; + + public void offset(int sec) { + this.valueInMillisecond += sec * 1000; + this.__recound_by_valueInMilliSecond(); + } + + public void offsetInMillisecond(int ms) { + this.valueInMillisecond += ms; + this.__recound_by_valueInMilliSecond(); + } + + private void __recound_by_valueInMilliSecond() { + // 确保毫秒数在一天之内,即 [0, 86399000] + if (this.valueInMillisecond >= 86400000) { + this.valueInMillisecond = this.valueInMillisecond % 86400000; + } + // 负数表示后退 + else if (this.valueInMillisecond < 0) { + this.valueInMillisecond = this.valueInMillisecond % 86400000; + if (this.valueInMillisecond < 0) { + this.valueInMillisecond = 86400000 + this.valueInMillisecond; + } + } + // 计算其他值 + this.value = this.valueInMillisecond / 1000; + this.millisecond = this.valueInMillisecond - this.value * 1000; + this.hour = Math.min(23, this.value / 3600); + this.minute = Math.min(59, (this.value - (this.hour * 3600)) / 60); + this.second = Math.min(59, this.value - (this.hour * 3600) - (this.minute * 60)); + } + + @Override + public String toString() { + String fmt = "HH:mm"; + // 到毫秒 + if (0 != this.millisecond) { + fmt += ":ss.SSS"; + } + // 到秒 + else if (0 != this.second) { + fmt += ":ss"; + } + return toString(fmt); + } + + private static Pattern _p_tmfmt = Pattern.compile("a|[HhKkms]{1,2}|S(SS)?"); + + /** + *
    +         * a    Am/pm marker (AM/PM)
    +         * H   Hour in day (0-23)
    +         * k   Hour in day (1-24)
    +         * K   Hour in am/pm (0-11)
    +         * h   Hour in am/pm (1-12)
    +         * m   Minute in hour
    +         * s   Second in minute
    +         * S   Millisecond Number
    +         * HH  补零的小时(0-23)
    +         * kk  补零的小时(1-24)
    +         * KK  补零的半天小时(0-11)
    +         * hh  补零的半天小时(1-12)
    +         * mm  补零的分钟
    +         * ss  补零的秒
    +         * SSS 补零的毫秒
    +         * 
    + * + * @param fmt + * 格式化字符串类似 "HH:mm:ss,SSS" + * @return 格式化后的时间 + */ + public String toString(String fmt) { + StringBuilder sb = new StringBuilder(); + fmt = Strings.sBlank(fmt, "HH:mm:ss"); + Matcher m = _p_tmfmt.matcher(fmt); + int pos = 0; + while (m.find()) { + int l = m.start(); + // 记录之前 + if (l > pos) { + sb.append(fmt.substring(pos, l)); + } + // 偏移 + pos = m.end(); + + // 替换 + String s = m.group(0); + if ("a".equals(s)) { + sb.append(this.value > 43200 ? "PM" : "AM"); + } + // H Hour in day (0-23) + else if ("H".equals(s)) { + sb.append(this.hour); + } + // k Hour in day (1-24) + else if ("k".equals(s)) { + sb.append(this.hour + 1); + } + // K Hour in am/pm (0-11) + else if ("K".equals(s)) { + sb.append(this.hour % 12); + } + // h Hour in am/pm (1-12) + else if ("h".equals(s)) { + sb.append((this.hour % 12) + 1); + } + // m Minute in hour + else if ("m".equals(s)) { + sb.append(this.minute); + } + // s Second in minute + else if ("s".equals(s)) { + sb.append(this.second); + } + // S Millisecond Number + else if ("S".equals(s)) { + sb.append(this.millisecond); + } + // HH 补零的小时(0-23) + else if ("HH".equals(s)) { + sb.append(String.format("%02d", this.hour)); + } + // kk 补零的小时(1-24) + else if ("kk".equals(s)) { + sb.append(String.format("%02d", this.hour + 1)); + } + // KK 补零的半天小时(0-11) + else if ("KK".equals(s)) { + sb.append(String.format("%02d", this.hour % 12)); + } + // hh 补零的半天小时(1-12) + else if ("hh".equals(s)) { + sb.append(String.format("%02d", (this.hour % 12) + 1)); + } + // mm 补零的分钟 + else if ("mm".equals(s)) { + sb.append(String.format("%02d", this.minute)); + } + // ss 补零的秒 + else if ("ss".equals(s)) { + sb.append(String.format("%02d", this.second)); + } + // SSS 补零的毫秒 + else if ("SSS".equals(s)) { + sb.append(String.format("%03d", this.millisecond)); + } + // 不认识 + else { + sb.append(s); + } + } + // 结尾 + if (pos < fmt.length()) { + sb.append(fmt.substring(pos)); + } + + // 返回 + return sb.toString(); + } + } + + /** + * 返回服务器当前时间 + * + * @return 服务器当前时间 + */ + public static Date now() { + return new Date(System.currentTimeMillis()); + } + + private static Pattern _P_TIME = Pattern.compile("^((\\d{2,4})([/\\\\-])?(\\d{1,2})([/\\\\-])?(\\d{1,2}))?" + + "(([ T])?" + + "(\\d{1,2})(:)(\\d{1,2})((:)(\\d{1,2}))?" + + "(([.])" + + "(\\d{1,}))?)?" + + "(([+-])(\\d{1,2})(:\\d{1,2})?)?" + + "$"); + + private static Pattern _P_TIME_LONG = Pattern.compile("^[0-9]+(L)?$"); + + /** + * 根据默认时区计算时间字符串的绝对毫秒数 + * + * @param ds + * 时间字符串 + * @return 绝对毫秒数 + * + * @see #ams(String, TimeZone) + */ + public static long ams(String ds) { + return ams(ds, null); + } + + /** + * 根据字符串得到相对于 "UTC 1970-01-01 00:00:00" 的绝对毫秒数。 + * 本函数假想给定的时间字符串是本地时间。所以计算出来结果后,还需要减去时差 + * + * 支持的时间格式字符串为: + * + *
    +     * yyyy-MM-dd HH:mm:ss
    +     * yyyy-MM-dd HH:mm:ss.SSS
    +     * yy-MM-dd HH:mm:ss;
    +     * yy-MM-dd HH:mm:ss.SSS;
    +     * yyyy-MM-dd;
    +     * yy-MM-dd;
    +     * HH:mm:ss;
    +     * HH:mm:ss.SSS;
    +     * 
    + * + * 时间字符串后面可以跟 +8 或者 +8:00 表示 GMT+8:00 时区。 同理 -9 或者 -9:00 表示 GMT-9:00 时区 + * + * @param ds + * 时间字符串 + * @param tz + * 你给定的时间字符串是属于哪个时区的 + * @return 时间 + * @see #_P_TIME + */ + public static long ams(String ds, TimeZone tz) { + Matcher m = _P_TIME.matcher(ds); + if (m.find()) { + int yy = _int(m, 2, 1970); + int MM = _int(m, 4, 1); + int dd = _int(m, 6, 1); + + int HH = _int(m, 9, 0); + int mm = _int(m, 11, 0); + int ss = _int(m, 14, 0); + + int ms = _int(m, 17, 0); + + /* + * zozoh: 先干掉,还是用 SimpleDateFormat 吧,"1980-05-01 15:17:23" 之前的日子 + * 得出的时间竟然总是多 30 分钟 long day = (long) D1970(yy, MM, dd); long MS = + * day * 86400000L; MS += (((long) HH) * 3600L + ((long) mm) * 60L + + * ss) * 1000L; MS += (long) ms; + * + * // 如果没有指定时区 ... if (null == tz) { // 那么用字符串中带有的时区信息, if + * (!Strings.isBlank(m.group(17))) { tz = + * TimeZone.getTimeZone(String.format("GMT%s%s:00", m.group(18), + * m.group(19))); // tzOffset = Long.parseLong(m.group(19)) // * + * 3600000L // * (m.group(18).charAt(0) == '-' ? -1 : 1); + * + * } // 如果依然木有,则用系统默认时区 else { tz = TimeZone.getDefault(); } } + * + * // 计算 return MS - tz.getRawOffset() - tz.getDSTSavings(); + */ + String str = String.format("%04d-%02d-%02d %02d:%02d:%02d.%03d", + yy, + MM, + dd, + HH, + mm, + ss, + ms); + SimpleDateFormat df = (SimpleDateFormat) DF_DATE_TIME_MS4.clone(); + // 那么用字符串中带有的时区信息 ... + if (null == tz && !Strings.isBlank(m.group(18))) { + tz = TimeZone.getTimeZone(String.format("GMT%s%s:00", m.group(19), m.group(20))); + } + // 指定时区 ... + if (null != tz) { + df.setTimeZone(tz); + } + // 解析返回 + try { + return df.parse(str).getTime(); + } + catch (ParseException e) { + throw Lang.wrapThrow(e); + } + } else if (_P_TIME_LONG.matcher(ds).find()) { + if (ds.endsWith("L")) { + ds.substring(0, ds.length() - 1); + } + return Long.parseLong(ds); + } + throw Lang.makeThrow("Unexpect date format '%s'", ds); + } + + /** + * 这个接口函数是 1.b.49 提供了,下一版本将改名为 ams,预计在版本 1.b.51 之后被移除 + * + * @deprecated since 1.b.49 util 1.b.51 + */ + @Deprecated + public static long ms(String ds, TimeZone tz) { + return ams(ds, tz); + } + + /** + * 返回时间对象在一天中的毫秒数 + * + * @param d + * 时间对象 + * + * @return 时间对象在一天中的毫秒数 + */ + public static long ms(Date d) { + return ms(C(d)); + } + + /** + * 返回时间对象在一天中的毫秒数 + * + * @param c + * 时间对象 + * + * @return 时间对象在一天中的毫秒数 + */ + public static int ms(Calendar c) { + int ms = c.get(Calendar.HOUR_OF_DAY) * 3600000; + ms += c.get(Calendar.MINUTE) * 60000; + ms += c.get(Calendar.SECOND) * 1000; + ms += c.get(Calendar.MILLISECOND); + return ms; + } + + /** + * 返回当前时间在一天中的毫秒数 + * + * @return 当前时间在一天中的毫秒数 + */ + public static int ms() { + return ms(Calendar.getInstance()); + } + + /** + * 返回当前时间在一天中的毫秒数 + * + * @param str + * 时间字符串 + * + * @return 当前时间在一天中的毫秒数 + */ + public static int ms(String str) { + return Ti(str).valueInMillisecond; + } + + /** + * 根据一个当天的绝对毫秒数,得到一个时间字符串,格式为 "HH:mm:ss.EEE" + * + * @param ms + * 当天的绝对毫秒数 + * @return 时间字符串 + */ + public static String mss(int ms) { + int sec = ms / 1000; + ms = ms - sec * 1000; + return secs(sec) + "." + Strings.alignRight(ms, 3, '0'); + } + + /** + * 根据一个当天的绝对秒数,得到一个时间字符串,格式为 "HH:mm:ss" + * + * @param sec + * 当天的绝对秒数 + * @return 时间字符串 + */ + public static String secs(int sec) { + int hh = sec / 3600; + sec -= hh * 3600; + int mm = sec / 60; + sec -= mm * 60; + return Strings.alignRight(hh, 2, '0') + + ":" + + Strings.alignRight(mm, 2, '0') + + ":" + + Strings.alignRight(sec, 2, '0'); + + } + + /** + * 返回时间对象在一天中的秒数 + * + * @param d + * 时间对象 + * + * @return 时间对象在一天中的秒数 + */ + public static int sec(Date d) { + Calendar c = C(d); + int sec = c.get(Calendar.HOUR_OF_DAY) * 3600; + sec += c.get(Calendar.MINUTE) * 60; + sec += c.get(Calendar.SECOND); + return sec; + } + + /** + * 返回当前时间在一天中的秒数 + * + * @return 当前时间在一天中的秒数 + */ + public static int sec() { + return sec(now()); + } + + /** + * 根据字符串得到时间对象 + * + * @param ds + * 时间字符串 + * @return 时间 + * + * @see #ams(String) + */ + public static Date D(String ds) { + return D(ams(ds)); + } + + private static int _int(Matcher m, int index, int dft) { + String s = m.group(index); + if (Strings.isBlank(s)) { + return dft; + } + return Integer.parseInt(s); + } + + // 常量数组,一年每个月多少天 + private static final int[] _MDs = new int[]{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + + /** + * 计算一个给定日期,距离 1970 年 1 月 1 日有多少天 + * + * @param yy + * 年,比如 1999,或者 43 + * @param MM + * 月,一月为 1,十二月为 12 + * @param dd + * 日,每月一号为 1 + * @return 距离 1970 年 1 月 1 日的天数 + */ + public static int D1970(int yy, int MM, int dd) { + // 转换成相对公元元年的年份 + // 如果给的年份小于 100,那么就认为是从 1970 开始算的年份 + int year = (yy < 100 ? yy + 1970 : yy); + // 得到今年之前的基本天数 + int day = (year - 1970) * 365; + // 补上闰年天数 + day += countLeapYear(year) - countLeapYear(1970); + // 计算今年本月之前的月份 + int mi = Math.min(MM - 1, 11); + boolean isLeapYear = leapYear(yy); + for (int i = 0; i < mi; i++) { + day += _MDs[i]; + } + // 考虑今年是闰年的情况 + if (isLeapYear && MM > 2) { + day++; + } + // 最后加上天数 + day += Math.min(dd, _MDs[mi]) - 1; + + // 如果是闰年且本月是 2 月 + if (isLeapYear && dd == 29) { + day++; + } + + // 如果是闰年并且过了二月 + return day; + } + + /** + * 根据毫秒数得到时间 + * + * @param ms + * 时间的毫秒数 + * @return 时间 + */ + public static Date D(long ms) { + return new Date(ms); + } + + /** + * 根据字符串得到时间 + * + *
    +     * 如果你输入了格式为 "yyyy-MM-dd HH:mm:ss"
    +     *    那么会匹配到秒
    +     *    
    +     * 如果你输入格式为 "yyyy-MM-dd"
    +     *    相当于你输入了 "yyyy-MM-dd 00:00:00"
    +     * 
    + * + * @param ds + * 时间字符串 + * @return 时间 + */ + public static Calendar C(String ds) { + return C(D(ds)); + } + + /** + * 根据日期对象得到时间 + * + * @param d + * 时间对象 + * @return 时间 + */ + public static Calendar C(Date d) { + return C(d.getTime()); + } + + /** + * 根据毫秒数得到时间 + * + * @param ms + * 时间的毫秒数 + * @return 时间 + */ + public static Calendar C(long ms) { + Calendar c = Calendar.getInstance(); + c.setTimeInMillis(ms); + return c; + } + + /** + * 把时间转换成格式为 y-M-d H:m:s.S 的字符串 + * + * @param d + * 时间对象 + * @return 该时间的字符串形式 , 格式为 y-M-d H:m:s.S + */ + public static String sDTms(Date d) { + return format(DF_DATE_TIME_MS, d); + } + + /** + * 把时间转换成格式为 yy-MM-dd HH:mm:ss.SSS 的字符串 + * + * @param d + * 时间对象 + * @return 该时间的字符串形式 , 格式为 yy-MM-dd HH:mm:ss.SSS + */ + public static String sDTms2(Date d) { + return format(DF_DATE_TIME_MS2, d); + } + + /** + * 把时间转换成格式为 yyyy-MM-dd HH:mm:ss.SSS 的字符串 + * + * @param d + * 时间对象 + * @return 该时间的字符串形式 , 格式为 yyyy-MM-dd HH:mm:ss.SSS + */ + public static String sDTms4(Date d) { + return format(DF_DATE_TIME_MS4, d); + } + + /** + * 把时间转换成格式为 yyyy-MM-dd HH:mm:ss 的字符串 + * + * @param d + * 时间对象 + * @return 该时间的字符串形式 , 格式为 yyyy-MM-dd HH:mm:ss + */ + public static String sDT(Date d) { + return format(DF_DATE_TIME, d); + } + + /** + * 把时间转换成格式为 yyyy-MM-dd 的字符串 + * + * @param d + * 时间对象 + * @return 该时间的字符串形式 , 格式为 yyyy-MM-dd + */ + public static String sD(Date d) { + return format(DF_DATE, d); + } + + /** + * 将一个秒数(天中),转换成一个格式为 HH:mm:ss 的字符串 + * + * @param sec + * 秒数 + * @return 格式为 HH:mm:ss 的字符串 + */ + public static String sT(int sec) { + int[] ss = T(sec); + return Strings.alignRight(ss[0], 2, '0') + + ":" + + Strings.alignRight(ss[1], 2, '0') + + ":" + + Strings.alignRight(ss[2], 2, '0'); + } + + /** + * 将一个秒数(天中),转换成一个格式为 HH:mm 的字符串(精确到分钟) + * + * @param sec + * 秒数 + * @return 格式为 HH:mm 的字符串 + */ + public static String sTmin(int sec) { + int[] ss = T(sec); + return Strings.alignRight(ss[0], 2, '0') + ":" + Strings.alignRight(ss[1], 2, '0'); + } + + /** + * 将一个毫秒秒数(天中),转换成一个格式为 HH:mm:ss,SSS 的字符串(精确到毫秒) + * + * @param ams + * 当天毫秒数 + * @return 格式为 HH:mm:ss,SSS 的字符串 + */ + public static String sTms(long ams) { + return Tims(ams).toString("HH:mm:ss,SSS"); + } + + /** + * 以本周为基础获得某一周的时间范围 + * + * @param off + * 从本周偏移几周, 0 表示本周,-1 表示上一周,1 表示下一周 + * + * @return 时间范围(毫秒级别) + * + * @see org.nutz.lang.Times#weeks(long, int, int) + */ + public static Date[] week(int off) { + return week(System.currentTimeMillis(), off); + } + + /** + * 以某周为基础获得某一周的时间范围 + * + * @param base + * 基础时间,毫秒 + * @param off + * 从本周偏移几周, 0 表示本周,-1 表示上一周,1 表示下一周 + * + * @return 时间范围(毫秒级别) + * + * @see org.nutz.lang.Times#weeks(long, int, int) + */ + public static Date[] week(long base, int off) { + return weeks(base, off, off); + } + + /** + * 以本周为基础获得时间范围 + * + * @param offL + * 从本周偏移几周, 0 表示本周,-1 表示上一周,1 表示下一周 + * @param offR + * 从本周偏移几周, 0 表示本周,-1 表示上一周,1 表示下一周 + * + * @return 时间范围(毫秒级别) + * + * @see org.nutz.lang.Times#weeks(long, int, int) + */ + public static Date[] weeks(int offL, int offR) { + return weeks(System.currentTimeMillis(), offL, offR); + } + + /** + * 按周获得某几周周一 00:00:00 到周六 的时间范围 + *

    + * 它会根据给定的 offL 和 offR 得到一个时间范围 + *

    + * 对本函数来说 week(-3,-5) 和 week(-5,-3) 是一个意思 + * + * @param base + * 基础时间,毫秒 + * @param offL + * 从本周偏移几周, 0 表示本周,-1 表示上一周,1 表示下一周 + * @param offR + * 从本周偏移几周, 0 表示本周,-1 表示上一周,1 表示下一周 + * + * @return 时间范围(毫秒级别) + */ + public static Date[] weeks(long base, int offL, int offR) { + int from = Math.min(offL, offR); + int len = Math.abs(offL - offR); + // 现在 + Calendar c = Calendar.getInstance(); + c.setTimeInMillis(base); + + Date[] re = new Date[2]; + + // 计算开始 + c.add(Calendar.DAY_OF_YEAR, 7 * from); + c.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY); + c.set(Calendar.HOUR_OF_DAY, 0); + c.set(Calendar.MINUTE, 0); + c.set(Calendar.SECOND, 0); + c.set(Calendar.MILLISECOND, 0); + re[0] = c.getTime(); + + // 计算结束 + c.add(Calendar.DAY_OF_YEAR, 7 * (len + 1)); + c.add(Calendar.MILLISECOND, -1); + re[1] = c.getTime(); + + // 返回 + return re; + } + + private static final String[] _MMM = new String[]{"Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec"}; + + /** + * 将一个时间格式化成容易被人类阅读的格式 + * + *

    +     * 如果 1 分钟内,打印 Just Now
    +     * 如果 1 小时内,打印多少分钟
    +     * 如果 1 天之内,打印多少小时之前
    +     * 如果是今年之内,打印月份和日期
    +     * 否则打印月份和年
    +     * 
    + * + * @param d + * @return 日期字符串 + */ + public static String formatForRead(Date d) { + long ms = System.currentTimeMillis() - d.getTime(); + // 如果 1 分钟内,打印 Just Now + if (ms < (60000)) { + return "Just Now"; + } + // 如果 1 小时内,打印多少分钟 + if (ms < (60 * 60000)) { + return "" + (ms / 60000) + "Min."; + } + + // 如果 1 天之内,打印多少小时之前 + if (ms < (24 * 3600 * 1000)) { + return "" + (ms / 3600000) + "hr."; + } + + // 如果一周之内,打印多少天之前 + if (ms < (7 * 24 * 3600 * 1000)) { + return "" + (ms / (24 * 3600000)) + "Day"; + } + + // 如果是今年之内,打印月份和日期 + Calendar c = Calendar.getInstance(); + int thisYear = c.get(Calendar.YEAR); + + c.setTime(d); + int yy = c.get(Calendar.YEAR); + int mm = c.get(Calendar.MONTH); + if (thisYear == yy) { + int dd = c.get(Calendar.DAY_OF_MONTH); + return String.format("%s %d", _MMM[mm], dd); + } + + // 否则打印月份和年 + return String.format("%s %d", _MMM[mm], yy); + } + + /** + * 以给定的时间格式来安全的对时间进行格式化,并返回格式化后所对应的字符串 + * + * @param fmt + * 时间格式 + * @param d + * 时间对象 + * @return 格式化后的字符串 + */ + public static String format(DateFormat fmt, Date d) { + return ((DateFormat) fmt.clone()).format(d); + } + + /** + * 以给定的时间格式来安全的对时间进行格式化,并返回格式化后所对应的字符串 + * + * @param fmt + * 时间格式 + * @param d + * 时间对象 + * @return 格式化后的字符串 + */ + public static String format(String fmt, Date d) { + return new SimpleDateFormat(fmt, Locale.ENGLISH).format(d); + } + + /** + * 以给定的时间格式来安全的解析时间字符串,并返回解析后所对应的时间对象(包裹RuntimeException) + * + * @param fmt + * 时间格式 + * @param s + * 时间字符串 + * @return 该时间字符串对应的时间对象 + */ + public static Date parseq(DateFormat fmt, String s) { + try { + return parse(fmt, s); + } + catch (ParseException e) { + throw Lang.wrapThrow(e); + } + } + + /** + * 以给定的时间格式来安全的解析时间字符串,并返回解析后所对应的时间对象(包裹RuntimeException) + * + * @param fmt + * 时间格式 + * @param s + * 时间字符串 + * @return 该时间字符串对应的时间对象 + */ + public static Date parseq(String fmt, String s) { + try { + return parse(fmt, s); + } + catch (ParseException e) { + throw Lang.wrapThrow(e); + } + } + + /** + * 以给定的时间格式来安全的解析时间字符串,并返回解析后所对应的时间对象 + * + * @param fmt + * 时间格式 + * @param s + * 日期时间字符串 + * @return 该时间字符串对应的时间对象 + */ + public static Date parse(DateFormat fmt, String s) throws ParseException { + return ((DateFormat) fmt.clone()).parse(s); + } + + /** + * 以给定的时间格式来安全的解析时间字符串,并返回解析后所对应的时间对象 + * + * @param fmt + * 时间格式 + * @param s + * 日期时间字符串 + * @return 该时间字符串对应的时间对象 + */ + public static Date parse(String fmt, String s) throws ParseException { + return new SimpleDateFormat(fmt).parse(s); + } + + private static final DateFormat DF_DATE_TIME_MS = new SimpleDateFormat("y-M-d H:m:s.S"); + private static final DateFormat DF_DATE_TIME_MS2 = new SimpleDateFormat("yy-MM-dd HH:mm:ss.SSS"); + private static final DateFormat DF_DATE_TIME_MS4 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + private static final DateFormat DF_DATE_TIME = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + private static final DateFormat DF_DATE = new SimpleDateFormat("yyyy-MM-dd"); + // private static final DateFormat DF_MONTH = new + // SimpleDateFormat("yyyy-MM"); + + public static final long T_1S = 1000; + public static final long T_1M = 60 * 1000; + public static final long T_1H = 60 * 60 * 1000; + public static final long T_1D = 24 * 60 * 60 * 1000; + + /** + * 方便的把时间换算成毫秒数 + * + * 支持几个单位, s(秒), m(分钟), h(小时), d(天) + * + * 比如: + * + * 100s -> 100000
    + * 2m -> 120000
    + * 3h -> 10800000
    + * + * @param tstr + * 时间字符串 + * @return 毫秒数 + */ + public static long toMillis(String tstr) { + if (Strings.isBlank(tstr)) { + return 0; + } + tstr = tstr.toLowerCase(); + // FIXME 稍后改成正则判断 + String tl = tstr.substring(0, tstr.length() - 1); + String tu = tstr.substring(tstr.length() - 1); + if (TIME_S_EN.equals(tu)) { + return T_1S * Long.valueOf(tl); + } + if (TIME_M_EN.equals(tu)) { + return T_1M * Long.valueOf(tl); + } + if (TIME_H_EN.equals(tu)) { + return T_1H * Long.valueOf(tl); + } + if (TIME_D_EN.equals(tu)) { + return T_1D * Long.valueOf(tl); + } + return Long.valueOf(tstr); + } + + private static String TIME_S_EN = "s"; + private static String TIME_M_EN = "m"; + private static String TIME_H_EN = "h"; + private static String TIME_D_EN = "d"; + + private static String TIME_S_CN = "秒"; + private static String TIME_M_CN = "分"; + private static String TIME_H_CN = "时"; + private static String TIME_D_CN = "天"; + + /** + * 一段时间长度的毫秒数转换为一个时间长度的字符串 + * + * 1000 -> 1S + * + * 120000 - 2M + * + * @param mi + * 毫秒数 + * @return 可读的文字 + */ + public static String fromMillis(long mi) { + return _fromMillis(mi, true); + } + + /** + * fromMillis的中文版本 + * + * 1000 -> 1秒 + * + * 120000 - 2分 + * + * @param mi + * 毫秒数 + * @return 可读的文字 + */ + public static String fromMillisCN(long mi) { + return _fromMillis(mi, false); + } + + private static String _fromMillis(long mi, boolean useEnglish) { + if (mi <= T_1S) { + return "1" + (useEnglish ? TIME_S_EN : TIME_S_CN); + } + if (mi < T_1M && mi > T_1S) { + return (int) (mi / T_1S) + (useEnglish ? TIME_S_EN : TIME_S_CN); + } + if (mi >= T_1M && mi < T_1H) { + int m = (int) (mi / T_1M); + return m + + (useEnglish ? TIME_M_EN : TIME_M_CN) + + _fromMillis(mi - m * T_1M, useEnglish); + } + if (mi >= T_1H && mi < T_1D) { + int h = (int) (mi / T_1H); + return h + + (useEnglish ? TIME_H_EN : TIME_H_CN) + + _fromMillis(mi - h * T_1H, useEnglish); + } + // if (mi >= T_1D) { + int d = (int) (mi / T_1D); + return d + (useEnglish ? TIME_D_EN : TIME_D_CN) + _fromMillis(mi - d * T_1D, useEnglish); + // } + // WTF ? + // throw Lang.impossible(); + } + + /** + * 比较2个字符串格式时间yyyy-MM-dd hh:mm:ss大小 2017-2-8 17:14:14 + * + * @param t1 + * 第一个时间 + * @param t2 + * 第二个时间 + * @return true,如果相等 + */ + public static boolean sDTcompare(String t1, String t2) { + // 将字符串形式的时间转化为Date类型的时间 + Date d1 = parseq(DF_DATE_TIME, t1); + Date d2 = parseq(DF_DATE_TIME, t2); + // Date类的一个方法,如果a早于b返回true,否则返回false + if (d1.before(d2)) { + return true; + } else { + return false; + } + } + + /** + * Unix时间戳转String日期 + * + * @param timestamp + * 时间戳 + * @param sf + * 日期格式 + * @return 日期字符串 + */ + public static String ts2S(long timestamp, String sf) { + DateFormat format = new SimpleDateFormat(sf); + return format.format(new Date(Long.parseLong(timestamp * 1000 + ""))); + } + + /** + * 取Unix时间戳 + * + * @return 时间戳 + */ + public static long getTS() { + return System.currentTimeMillis() / 1000; + } + + /** + * 字符串yyyy-MM-dd HH:mm:ss时间转化成Unix时间戳 + * + * @param str + * 日期,符合yyyy-MM-dd HH:mm:ss + * @return timestamp 时间戳字符串 + */ + public static String sDT2TS(String str, DateFormat df) { + try { + return "" + (df.parse(str).getTime() / 1000); + } + catch (Exception e) { + e.printStackTrace(); + } + return "0"; + } + + /** + * 取当前时间的字符串形式 , 格式为 yyyy-MM-dd HH:mm:ss + * + * @return 时间字符串 + */ + public static String getNowSDT() { + return sDT(now()); + } + + /** + * 获得某月的天数 + * + * @param year + * 年 + * @param month + * 月 + * @return int 指定年月的天数 + */ + public static int getDaysOfMonth(String year, String month) { + int days = 0; + if ("1".equals(month) + || "3".equals(month) + || "5".equals(month) + || "7".equals(month) + || "8".equals(month) + || "10".equals(month) + || "12".equals(month)) { + days = 31; + } else if ("4".equals(month) + || "6".equals(month) + || "9".equals(month) + || "11".equals(month)) { + days = 30; + } else { + if ((Integer.parseInt(year) % 4 == 0 && Integer.parseInt(year) % 100 != 0) + || Integer.parseInt(year) % 400 == 0) { + days = 29; + } else { + days = 28; + } + } + return days; + } + + /** + * 获取某年某月的天数 + * + * @param year + * int 年 + * @param month + * int 月份[1-12] 月 + * @return int 指定年月的天数 + */ + public static int getDaysOfMonth(int year, int month) { + Calendar calendar = Calendar.getInstance(); + calendar.set(year, month - 1, 1); + return calendar.getActualMaximum(Calendar.DAY_OF_MONTH); + } + + /** + * 获得当前日期 + * + * @return 当前日期,按月算,即DAY_OF_MONTH + */ + public static int getToday() { + Calendar calendar = Calendar.getInstance(); + return calendar.get(Calendar.DATE); + } + + /** + * 获得当前月份 + * + * @return 当前月份,1开始算 + */ + public static int getToMonth() { + Calendar calendar = Calendar.getInstance(); + return calendar.get(Calendar.MONTH) + 1; + } + + /** + * 获得当前年份 + * + * @return 当前年份 + */ + public static int getToYear() { + Calendar calendar = Calendar.getInstance(); + return calendar.get(Calendar.YEAR); + } + + /** + * 返回日期的天 + * + * @param date + * 指定的Date + * @return 指定时间所在月的DAY_OF_MONTH + */ + public static int getDay(Date date) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + return calendar.get(Calendar.DATE); + } + + /** + * 返回日期的年 + * + * @param date + * 指定的Date + * @return 指定时间的年份 + */ + public static int getYear(Date date) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + return calendar.get(Calendar.YEAR); + } + + /** + * 返回日期的月份,1-12 + * + * @param date + * 指定的Date + * @return 指定时间的月份 + */ + public static int getMonth(Date date) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + return calendar.get(Calendar.MONTH) + 1; + } + + /** + * 计算两个日期相差的天数,如果date2 > date1 返回正数,否则返回负数 + * + * @param date1 + * Date + * @param date2 + * Date + * @return long + */ + public static long dayDiff(Date date1, Date date2) { + return (date2.getTime() - date1.getTime()) / 86400000; + } + + /** + * 比较两个日期的年差 + * + * @param before + * 前一个日期,格式yyyy-MM-dd + * @param after + * 后一个日期,格式yyyy-MM-dd + * @return 年份差值 + */ + public static int yearDiff(String before, String after) { + Date beforeDay = parseq(DF_DATE, before); + Date afterDay = parseq(DF_DATE, after); + return getYear(afterDay) - getYear(beforeDay); + } + + /** + * 比较指定日期与当前日期的年差 + * + * @param after + * 指定的后一个日期,格式yyyy-MM-dd + * @return 年份差值 + */ + public static int yearDiffCurr(String after) { + Date beforeDay = new Date(); + Date afterDay = parseq(DF_DATE, after); + return getYear(beforeDay) - getYear(afterDay); + } + + /** + * 比较指定日期与当前日期的天差 + * + * @param before + * 指定的前应日期,格式yyyy-MM-dd + * @return 天差 + */ + public static long dayDiffCurr(String before) { + Date currDate = parseq(DF_DATE, sD(now())); + Date beforeDate = parseq(DF_DATE, before); + return (currDate.getTime() - beforeDate.getTime()) / 86400000; + + } + + /** + * 根据生日获取星座 + * + * @param birth + * 日期格式为YYYY-mm-dd + * @return 星座,单一字符 + */ + public static String getAstro(String birth) { + if (!isDate(birth)) { + birth = "2000" + birth; + } + if (!isDate(birth)) { + return ""; + } + int month = Integer.parseInt(birth.substring(birth.indexOf("-") + 1, + birth.lastIndexOf("-"))); + int day = Integer.parseInt(birth.substring(birth.lastIndexOf("-") + 1)); + String s = "魔羯水瓶双鱼牡羊金牛双子巨蟹狮子处女天秤天蝎射手魔羯"; + int[] arr = {20, 19, 21, 21, 21, 22, 23, 23, 23, 23, 22, 22}; + int start = month * 2 - (day < arr[month - 1] ? 2 : 0); + return s.substring(start, start + 2) + "座"; + } + + /** + * 判断日期是否有效,包括闰年的情况 + * + * @param date + * 日期格式YYYY-mm-dd + * @return true,如果合法 + */ + public static boolean isDate(String date) { + StringBuffer reg = new StringBuffer("^((\\d{2}(([02468][048])|([13579][26]))-?((((0?"); + reg.append("[13578])|(1[02]))-?((0?[1-9])|([1-2][0-9])|(3[01])))"); + reg.append("|(((0?[469])|(11))-?((0?[1-9])|([1-2][0-9])|(30)))|"); + reg.append("(0?2-?((0?[1-9])|([1-2][0-9])))))|(\\d{2}(([02468][12"); + reg.append("35679])|([13579][01345789]))-?((((0?[13578])|(1[02]))"); + reg.append("-?((0?[1-9])|([1-2][0-9])|(3[01])))|(((0?[469])|(11))"); + reg.append("-?((0?[1-9])|([1-2][0-9])|(30)))|(0?2-?((0?["); + reg.append("1-9])|(1[0-9])|(2[0-8]))))))"); + Pattern p = Regex.getPattern(reg.toString()); + return p.matcher(date).matches(); + } + + /** + * 取得指定日期过 years 年后的日期 (当 years 为负数表示指定年之前); + * + * @param date + * 日期 为null时表示当天 + * @param years + * 相加(相减)的年数 + */ + public static Date nextYear(Date date, int years) { + Calendar cal = Calendar.getInstance(); + if (date != null) { + cal.setTime(date); + } + cal.add(Calendar.YEAR, years); + return cal.getTime(); + } + + /** + * 取得指定日期过 months 月后的日期 (当 months 为负数表示指定月之前); + * + * @param date + * 日期 为null时表示当天 + * @param months + * 相加(相减)的月数 + */ + public static Date nextMonth(Date date, int months) { + Calendar cal = Calendar.getInstance(); + if (date != null) { + cal.setTime(date); + } + cal.add(Calendar.MONTH, months); + return cal.getTime(); + } + + /** + * 取得指定日期过 day 周后的日期 (当 day 为负数表示指定月之前) + * + * @param date + * 日期 为null时表示当天 + */ + public static Date nextWeek(Date date, int week) { + Calendar cal = Calendar.getInstance(); + if (date != null) { + cal.setTime(date); + } + cal.add(Calendar.WEEK_OF_MONTH, week); + return cal.getTime(); + } + + /** + * 取得指定日期过 day 天后的日期 (当 day 为负数表示指日期之前); + * + * @param date + * 日期 为null时表示当天 + * @param day + * 相加(相减)的月数 + */ + public static Date nextDay(Date date, int day) { + Calendar cal = Calendar.getInstance(); + if (date != null) { + cal.setTime(date); + } + cal.add(Calendar.DAY_OF_MONTH, day); + return cal.getTime(); + } + + /** + * 取得当前时间距离1900/1/1的天数 + * + * @return 天数 + */ + public static int getDayNum() { + int daynum = 0; + GregorianCalendar gd = new GregorianCalendar(); + Date dt = gd.getTime(); + GregorianCalendar gd1 = new GregorianCalendar(1900, 1, 1); + Date dt1 = gd1.getTime(); + daynum = (int) ((dt.getTime() - dt1.getTime()) / (24 * 60 * 60 * 1000)); + return daynum; + } + + /** + * getDayNum的逆方法(用于处理Excel取出的日期格式数据等) + * + * @param day + * 天数 + * @return 反推出的时间 + */ + public static Date getDateByNum(int day) { + GregorianCalendar gd = new GregorianCalendar(1900, 1, 1); + Date date = gd.getTime(); + date = nextDay(date, day); + return date; + } + + /** + * 取得距离今天 day 日的日期 + * + * @param day + * 天数 + * @return 日期字符串 + */ + public static String nextDay(int day) { + Calendar cal = Calendar.getInstance(); + cal.setTime(now()); + cal.add(Calendar.DAY_OF_MONTH, day); + return format(DF_DATE, cal.getTime()); + } + + /** + * 获取明天的日期 + * + * return 明天的日期 + */ + public static String afterDay() { + return nextDay(1); + } + + /** + * 获取昨天的日期 + * + * @return 昨天的日期 + */ + public static String befoDay() { + return nextDay(-1); + } + + /** + * 获取本月最后一天 + * + * @return 本月最后一天 + */ + public static String getLastDayOfMonth() { + Calendar cal = Calendar.getInstance(); + cal.set(Calendar.DATE, 1); + cal.add(Calendar.MONTH, 1); + cal.add(Calendar.DATE, -1); + return format(DF_DATE, cal.getTime()); + } + + /** + * 获取本月第一天 + * + * @return 本月第一天 + */ + public static String getFirstDayOfMonth() { + Calendar cal = Calendar.getInstance(); + cal.set(Calendar.DATE, 1); + return format(DF_DATE, cal.getTime()); + } + + public static final long T_1MS = 1; + public static final long T_1W = 7 * 24 * 60 * 60 * 1000; + + /** + * 判断两个日期相差的时长 + * + * @param s + * 起始日期 + * @param e + * 结束日期 + * @param unit + * 相差的单位 T_1MS 毫秒 T_1S 秒 T_1M 分 T_1H 时 T_1D 天 T_1W 周 + * @return 相差的数量 + */ + public static long between(Date s, Date e, long unit) { + + Date start; + Date end; + if (s.before(e)) { + start = s; + end = e; + } else { + start = e; + end = s; + } + long diff = end.getTime() - start.getTime(); + return diff / unit; + } + + /** + * 取得指定日期过 minute 分钟后的日期 (当 minute 为负数表示指定分钟之前) + * + * @param date + * 日期 为null时表示当天 + */ + public static Date nextMinute(Date date, int minute) { + Calendar cal = Calendar.getInstance(); + if (date != null) { + cal.setTime(date); + } + cal.add(Calendar.MINUTE, minute); + return cal.getTime(); + } + + /** + * 取得指定日期过 second 秒后的日期 (当 second 为负数表示指定秒之前) + * + * @param date + * 日期 为null时表示当天 + */ + public static Date nextSecond(Date date, int second) { + Calendar cal = Calendar.getInstance(); + if (date != null) { + cal.setTime(date); + } + cal.add(Calendar.SECOND, second); + return cal.getTime(); + } + + /** + * 取得指定日期过 hour 小时后的日期 (当 hour 为负数表示指定小时之前) + * + * @param date + * 日期 为null时表示当天 + */ + public static Date nextHour(Date date, int hour) { + Calendar cal = Calendar.getInstance(); + if (date != null) { + cal.setTime(date); + } + cal.add(Calendar.HOUR, hour); + return cal.getTime(); + } + + /** + * Unix时间戳转Date日期 + * + * @param timestamp + * 时间戳 + * @return 日期 + */ + public static Date ts2D(long timestamp) { + return new Date(Long.parseLong(timestamp * 1000 + "")); + } + + /** + * Date日期转Unix时间戳 + * + * @param date 日期 + * @return 时间戳 + */ + public static long d2TS(Date date) { + if (Lang.isEmpty(date)) { + return getTS(); + } else { + return date.getTime() / 1000; + } + } +} diff --git a/src/org/nutz/lang/Xmls.java b/src/org/nutz/lang/Xmls.java index 6456776a33..bdffedbf33 100644 --- a/src/org/nutz/lang/Xmls.java +++ b/src/org/nutz/lang/Xmls.java @@ -24,6 +24,7 @@ import org.nutz.lang.util.Callback2; import org.nutz.lang.util.NutMap; +import org.nutz.lang.util.Regex; import org.nutz.lang.util.Tag; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -47,7 +48,40 @@ public abstract class Xmls { * @throws ParserConfigurationException */ public static DocumentBuilder xmls() throws ParserConfigurationException { - return DocumentBuilderFactory.newInstance().newDocumentBuilder(); + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + String FEATURE = null; + + // This is the PRIMARY defense. If DTDs (doctypes) are disallowed, almost all XML entity attacks are prevented + // Xerces 2 only - http://xerces.apache.org/xerces2-j/features.html#disallow-doctype-decl + + FEATURE = "http://apache.org/xml/features/disallow-doctype-decl"; + dbf.setFeature(FEATURE, true); + + // If you can't completely disable DTDs, then at least do the following: + // Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-general-entities + + // Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-general-entities + + // JDK7+ - http://xml.org/sax/features/external-general-entities + FEATURE = "http://xml.org/sax/features/external-general-entities"; + dbf.setFeature(FEATURE, false); + + // Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-parameter-entities + + // Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-parameter-entities + + // JDK7+ - http://xml.org/sax/features/external-parameter-entities + FEATURE = "http://xml.org/sax/features/external-parameter-entities"; + dbf.setFeature(FEATURE, false); + + // Disable external DTDs as well + FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd"; + dbf.setFeature(FEATURE, false); + + // and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks" + dbf.setXIncludeAware(false); + dbf.setExpandEntityReferences(false); + return dbf.newDocumentBuilder(); } public static Document xml(InputStream ins) { @@ -356,7 +390,7 @@ public static boolean hasChild(Element ele, String regex) { if (nd instanceof Element) { if (null == regex) return false; - if (((Element) nd).getTagName().matches(regex)) + if (Regex.match(regex, ((Element) nd).getTagName())) return true; } } @@ -493,19 +527,28 @@ public static NutMap asMap(Element ele, final boolean lowFirst, final boolean du return asMap(ele, lowFirst, dupAsList, null); } public static NutMap asMap(Element ele, final boolean lowerFirst, final boolean dupAsList, final List alwaysAsList) { + return asMap(ele, new XmlParserOpts(lowerFirst, dupAsList, alwaysAsList, false)); + } + public static NutMap asMap(Element ele, final XmlParserOpts opts) { final NutMap map = new NutMap(); + if (opts.isAttrAsKeyValue()) { + NamedNodeMap attrs = ele.getAttributes(); + for (int i = 0; i < attrs.getLength(); i++) { + map.put(attrs.item(i).getNodeName(), attrs.item(i).getNodeValue()); + } + } eachChildren(ele, new Each() { public void invoke(int index, Element _ele, int length) throws ExitLoop, ContinueLoop, LoopException { String key = _ele.getNodeName(); - if (lowerFirst) + if (opts.lowerFirst) key = Strings.lowerFirst(key); - Map tmp = asMap(_ele, lowerFirst, dupAsList, alwaysAsList); + Map tmp = asMap(_ele, opts); if (!tmp.isEmpty()) { - if (alwaysAsList != null && alwaysAsList.contains(key)) { + if (opts.alwaysAsList != null && opts.alwaysAsList.contains(key)) { map.addv2(key, tmp); } - else if (dupAsList) { + else if (opts.dupAsList) { map.addv(key, tmp); } else { @@ -514,11 +557,11 @@ else if (dupAsList) { return; } String val = getText(_ele); - if (!Strings.isBlank(val)) { - if (alwaysAsList != null && alwaysAsList.contains(key)) { - map.addv2(key, map); + if (opts.keeyBlankNode || !Strings.isBlank(val)) { + if (opts.alwaysAsList != null && opts.alwaysAsList.contains(key)) { + map.addv2(key, val); } - else if (dupAsList) + else if (opts.dupAsList) map.addv(key, val); else map.setv(key, val); @@ -644,4 +687,60 @@ public static List getEles(Element ele, String xpath) { } public static String HEAD = ""; + + public static class XmlParserOpts { + private boolean lowerFirst; + private boolean dupAsList; + private List alwaysAsList; + private boolean keeyBlankNode; + private boolean attrAsKeyValue; + public XmlParserOpts() { + } + + + public XmlParserOpts(boolean lowerFirst, boolean dupAsList, List alwaysAsList, boolean keeyBlankNode) { + super(); + this.lowerFirst = lowerFirst; + this.dupAsList = dupAsList; + this.alwaysAsList = alwaysAsList; + this.keeyBlankNode = keeyBlankNode; + } + + + public boolean isLowerFirst() { + return lowerFirst; + } + public void setLowerFirst(boolean lowerFirst) { + this.lowerFirst = lowerFirst; + } + public boolean isDupAsList() { + return dupAsList; + } + public void setDupAsList(boolean dupAsList) { + this.dupAsList = dupAsList; + } + public List getAlwaysAsList() { + return alwaysAsList; + } + public void setAlwaysAsList(List alwaysAsList) { + this.alwaysAsList = alwaysAsList; + } + public boolean isKeeyBlankNode() { + return keeyBlankNode; + } + public void setKeeyBlankNode(boolean keeyBlankNode) { + this.keeyBlankNode = keeyBlankNode; + } + + + public boolean isAttrAsKeyValue() { + return attrAsKeyValue; + } + + + public void setAttrAsKeyValue(boolean attrAsKeyValue) { + this.attrAsKeyValue = attrAsKeyValue; + } + + } } diff --git a/src/org/nutz/lang/eject/EjectByGetter.java b/src/org/nutz/lang/eject/EjectByGetter.java index 2e4f109ae7..56a8fc16fc 100644 --- a/src/org/nutz/lang/eject/EjectByGetter.java +++ b/src/org/nutz/lang/eject/EjectByGetter.java @@ -30,6 +30,8 @@ public Object eject(Object obj) { if (NutConf.USE_FASTCLASS) { if (fm == null) fm = FastClassFactory.get(getter); + if (fm == null) + return getter.invoke(obj); return fm.invoke(obj); } return getter.invoke(obj); diff --git a/src/org/nutz/lang/hardware/NetworkItem.java b/src/org/nutz/lang/hardware/NetworkItem.java index d2d80bb902..47d9e4670c 100644 --- a/src/org/nutz/lang/hardware/NetworkItem.java +++ b/src/org/nutz/lang/hardware/NetworkItem.java @@ -1,19 +1,35 @@ package org.nutz.lang.hardware; +import org.nutz.lang.Strings; + public class NetworkItem { + private String name; + private String ipv4; private String ipv6; private String mac; - + private int mtu; - + private int signal; - + private String display; + public boolean hasName() { + return !Strings.isBlank(name); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + public String getIpv4() { return ipv4; } @@ -38,21 +54,21 @@ public void setMac(String mac) { this.mac = mac; } - public int getMtu() { - return mtu; - } + public int getMtu() { + return mtu; + } - public void setMtu(int mtu) { - this.mtu = mtu; - } + public void setMtu(int mtu) { + this.mtu = mtu; + } - public int getSignal() { - return signal; - } - - public void setSignal(int signal) { - this.signal = signal; - } + public int getSignal() { + return signal; + } + + public void setSignal(int signal) { + this.signal = signal; + } public String getDisplay() { return display; @@ -61,4 +77,13 @@ public String getDisplay() { public void setDisplay(String display) { this.display = display; } + + public String toString() { + return String.format("%s:%s: (%s/%s) %s", + this.name, + this.mac, + this.ipv4, + this.ipv6, + this.display); + } } diff --git a/src/org/nutz/lang/hardware/Networks.java b/src/org/nutz/lang/hardware/Networks.java index 31c9127a1e..6049db9a88 100644 --- a/src/org/nutz/lang/hardware/Networks.java +++ b/src/org/nutz/lang/hardware/Networks.java @@ -1,7 +1,9 @@ package org.nutz.lang.hardware; +import java.net.InetAddress; import java.net.InterfaceAddress; import java.net.NetworkInterface; +import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; @@ -22,7 +24,7 @@ public class Networks { private static Map ntMap = new HashMap(); static { - ntMap.put(NetworkType.LAN, "eth, en"); + ntMap.put(NetworkType.LAN, "eth, en, em"); ntMap.put(NetworkType.WIFI, "wlan"); ntMap.put(NetworkType.ThreeG, "ppp"); ntMap.put(NetworkType.VPN, "tun"); @@ -50,9 +52,14 @@ public static Map networkItems() { List addrs = face.getInterfaceAddresses(); if (addrs != null && !addrs.isEmpty()) { for (InterfaceAddress interfaceAddress : addrs) { - String ip = interfaceAddress.getAddress().getHostAddress(); + InetAddress iaddr = interfaceAddress.getAddress(); + String ip = iaddr.getHostAddress(); if (ip == null || ip.length() == 0) continue; + + if (!netItem.hasName()) + netItem.setName(iaddr.getHostName()); + if (ip.contains(".")) netItem.setIpv4(ip); else @@ -61,9 +68,16 @@ public static Map networkItems() { } netItem.setMtu(face.getMTU()); netItem.setDisplay(face.getDisplayName()); - - if (netItem.getIpv4() == null && netItem.getMac() == null && netItem.getMtu() < 1 && !face.getName().startsWith("eth")) - continue; + + if (!netItem.hasName()) { + netItem.setName(face.getName()); + } + + if (netItem.getIpv4() == null + && netItem.getMac() == null + && netItem.getMtu() < 1 + && !face.getName().startsWith("eth")) + continue; netFaces.put(face.getName(), netItem); } } @@ -80,6 +94,34 @@ public static Map networkItems() { return netFaces; } + /** + * @return 返回当前第一个可用的HostName + */ + public static String hostName() { + try { + InetAddress ia = InetAddress.getLocalHost(); + if (null != ia) { + return ia.getHostName(); + } + } + catch (UnknownHostException e) {} + + Map items = networkItems(); + // 先遍历一次eth开头的 + for (int i = 0; i < 10; i++) { + NetworkItem item = items.get("eth" + i); + if (null != item && item.hasName()) { + return item.getName(); + } + } + for (NetworkItem item : items.values()) { + if (null != item && item.hasName()) { + return item.getName(); + } + } + return null; + } + /** * @return 返回当前第一个可用的IP地址 */ @@ -87,19 +129,19 @@ public static String ipv4() { Map items = networkItems(); // 先遍历一次eth开头的 for (int i = 0; i < 10; i++) { - NetworkItem item = items.get("eth"+i); + NetworkItem item = items.get("eth" + i); if (item != null) { String ip = item.getIpv4(); if (ipOk(ip)) return ip; } } - for (NetworkItem item : items.values()) { - String ip = item.getIpv4(); - if (ipOk(ip)) - return ip; - } - return null; + for (NetworkItem item : items.values()) { + String ip = item.getIpv4(); + if (ipOk(ip)) + return ip; + } + return null; } /** @@ -123,10 +165,16 @@ public static String ipv4(NetworkType nt) { * @return 返回当前第一个可用的MAC地址 */ public static String mac() { - NetworkItem networkItem = firstNetwokrItem(); - if (networkItem == null) - return null; - return networkItem.getMac(); + String mac = mac(NetworkType.LAN); + if (mac != null) + return mac; + mac = mac(NetworkType.WIFI); + if (mac != null) + return mac; + NetworkItem network = firstNetwokrItem(); + if (network != null) + return network.getMac(); + return null; } /** @@ -164,18 +212,19 @@ private static NetworkItem firstNetwokrItem() { re = getNetworkByTypes(netFaces, ntMap.get(NetworkType.VPN)); } if (re.isEmpty()) { - for (Entry en : netFaces.entrySet()) { - if (Strings.isBlank(en.getValue().getIpv4())) - continue; - if (Strings.isBlank(en.getValue().getMac())) - continue; - return en.getValue(); - } + for (Entry en : netFaces.entrySet()) { + if (Strings.isBlank(en.getValue().getIpv4())) + continue; + if (Strings.isBlank(en.getValue().getMac())) + continue; + return en.getValue(); + } } return re.get(0); } - private static List getNetworkByTypes(Map netFaces, String nt) { + private static List getNetworkByTypes(Map netFaces, + String nt) { List list = new ArrayList(); String[] nss = Strings.splitIgnoreBlank(nt, ","); for (String ns : nss) { @@ -186,7 +235,7 @@ private static List getNetworkByTypes(Map netF } return list; } - + public static boolean ipOk(String ip) { return (!Strings.isBlank(ip) && !ip.startsWith("127.0") && !ip.startsWith("169.")); } diff --git a/src/org/nutz/lang/random/R.java b/src/org/nutz/lang/random/R.java index 5864cdcd80..7aa6e62624 100644 --- a/src/org/nutz/lang/random/R.java +++ b/src/org/nutz/lang/random/R.java @@ -17,7 +17,7 @@ */ public abstract class R { - static Random r = new Random(); + static Random r = new Random(System.currentTimeMillis()); /** * 根据一个范围,生成一个随机的整数 @@ -59,6 +59,7 @@ public static StringGenerator sg(int len) { private static final char[] _UU64 = "-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz".toCharArray(); private static final char[] _UU32 = "0123456789abcdefghijklmnopqrstuv".toCharArray(); + private static final char[] _C = "23456789abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ".toCharArray(); /** * @return 64进制表示的紧凑格式的 UUID @@ -259,27 +260,9 @@ public static String captchaChar(int length) { */ public static String captchaChar(int length, boolean caseSensitivity) { StringBuilder sb = new StringBuilder(); - Random rand = new Random();// 随机用以下三个随机生成器 - Random randdata = new Random(); - int data = 0; - for (int i = 0; i < length; i++) { - int index = rand.nextInt(caseSensitivity ? 3 : 2); - // 目的是随机选择生成数字,大小写字母 - switch (index) { - case 0: - data = randdata.nextInt(10);// 仅仅会生成0~9, 0~9的ASCII为48~57 - sb.append(data); - break; - case 1: - data = randdata.nextInt(26) + 97;// 保证只会产生ASCII为97~122(a-z)之间的整数, - sb.append((char) data); - break; - case 2: // caseSensitivity为true的时候, 才会有大写字母 - data = randdata.nextInt(26) + 65;// 保证只会产生ASCII为65~90(A~Z)之间的整数 - sb.append((char) data); - break; - } - } + int t = caseSensitivity ? _C.length : _C.length - 24; + for (int i = 0; i < length; i++) + sb.append(_C[r.nextInt(t)]); return sb.toString(); } diff --git a/src/org/nutz/lang/reflect/FastMethodFactory.java b/src/org/nutz/lang/reflect/FastMethodFactory.java index ede69509dd..e7aaa4bc3c 100644 --- a/src/org/nutz/lang/reflect/FastMethodFactory.java +++ b/src/org/nutz/lang/reflect/FastMethodFactory.java @@ -29,14 +29,18 @@ public class FastMethodFactory implements Opcodes { protected static FastMethod make(final Method method) { Class klass = method.getDeclaringClass(); - String descriptor = Type.getMethodDescriptor(method); + String descriptor = Type.getMethodDescriptor(method) + method.getDeclaringClass().getClassLoader(); String key = "$FM$" + method.getName() + "$" + Lang.md5(descriptor); - String className = klass.getName() + key; - if (klass.getName().startsWith("java")) - className = FastMethod.class.getPackage().getName() + ".fast." + className; + String className = ReflectTool.class.getPackage().getName() + "." + Lang.md5(klass.getName()) + key; FastMethod fm = cache.get(className); if (fm != null) return fm; + // fix issue #1382 : 非public类的方法,统统做成FallbackFastMethod + if (!Modifier.isPublic(klass.getModifiers())) { + fm = new FallbackFastMethod(method); + cache.put(className, fm); + return fm; + } try { fm = (FastMethod) klass.getClassLoader().loadClass(className).newInstance(); cache.put(className, fm); @@ -67,11 +71,9 @@ protected static FastMethod make(final Method method) { protected static FastMethod make(Constructor constructor) { Class klass = constructor.getDeclaringClass(); - String descriptor = Type.getConstructorDescriptor(constructor); + String descriptor = Type.getConstructorDescriptor(constructor) + constructor.getDeclaringClass().getClassLoader();; String key = Lang.md5(descriptor); - String className = klass.getName() + "$FC$" + key; - if (klass.getName().startsWith("java")) - className = FastMethod.class.getPackage().getName() + ".fast." + className; + String className = ReflectTool.class.getPackage().getName() + "." + Lang.md5(klass.getName()) + "$FC$" + key; FastMethod fm = (FastMethod) cache.get(className); if (fm != null) return fm; diff --git a/src/org/nutz/lang/reflect/ReflectTool.java b/src/org/nutz/lang/reflect/ReflectTool.java index 9d9d7ba95c..1a239f57b9 100644 --- a/src/org/nutz/lang/reflect/ReflectTool.java +++ b/src/org/nutz/lang/reflect/ReflectTool.java @@ -1,7 +1,6 @@ package org.nutz.lang.reflect; -import org.nutz.lang.Lang; - +import java.lang.invoke.MethodHandles; import java.lang.reflect.Field; import java.lang.reflect.GenericDeclaration; import java.lang.reflect.Method; @@ -12,40 +11,44 @@ import java.security.PrivilegedAction; import java.security.ProtectionDomain; +import org.nutz.lang.Lang; + @SuppressWarnings({"unchecked", "rawtypes"}) public class ReflectTool { - - private static Method DEFINE_CLASS; - private static final ProtectionDomain PROTECTION_DOMAIN; - - static { - PROTECTION_DOMAIN = getProtectionDomain(ReflectTool.class); - - AccessController.doPrivileged(new PrivilegedAction() { - public Object run() { - try { - Class loader = Class.forName("java.lang.ClassLoader"); // JVM - // crash - // w/o - // this - DEFINE_CLASS = loader.getDeclaredMethod("defineClass", - new Class[]{String.class, - byte[].class, - Integer.TYPE, - Integer.TYPE, - ProtectionDomain.class}); - DEFINE_CLASS.setAccessible(true); - } - catch (ClassNotFoundException e) { - // Lang.impossible(); - } - catch (NoSuchMethodException e) { - // Lang.impossible(); - } - return null; - } - }); - } + + protected static boolean hasLookup = false; + protected static Method DEFINE_CLASS; + protected static ProtectionDomain PROTECTION_DOMAIN; + static { + try { + MethodHandles.lookup(); + hasLookup = true; + } + catch (Throwable e) { + PROTECTION_DOMAIN = getProtectionDomain(ReflectTool.class); + AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + try { + Class loader = Class.forName("java.lang.ClassLoader"); // JVM + // crash + // w/o + // this + DEFINE_CLASS = loader.getDeclaredMethod("defineClass", + new Class[]{String.class, + byte[].class, + Integer.TYPE, + Integer.TYPE, + ProtectionDomain.class}); + DEFINE_CLASS.setAccessible(true); + } + catch (Throwable e) { + // Lang.impossible(); + } + return null; + } + }); + } + } public static ProtectionDomain getProtectionDomain(final Class source) { if (source == null) { @@ -67,13 +70,20 @@ public static Class defineClass(String className, byte[] b, ClassLoader loader, ProtectionDomain protectionDomain) throws Exception { + if (hasLookup) { + Class c = MethodHandles.lookup().defineClass(b); + Class.forName(className, true, loader); + return c; + } + if (DEFINE_CLASS == null) + throw Lang.impossible(); Object[] args = new Object[]{className, - b, - new Integer(0), - new Integer(b.length), - protectionDomain}; + b, + new Integer(0), + new Integer(b.length), + protectionDomain}; if (loader == null) - loader = ReflectTool.class.getClassLoader(); + loader = ReflectTool.class.getClassLoader(); Class c = (Class) DEFINE_CLASS.invoke(loader, args); // Force static initializers to run. Class.forName(className, true, loader); diff --git a/src/org/nutz/lang/socket/SocketContext.java b/src/org/nutz/lang/socket/SocketContext.java index 99cf5ceb9f..7bbfe84ff0 100644 --- a/src/org/nutz/lang/socket/SocketContext.java +++ b/src/org/nutz/lang/socket/SocketContext.java @@ -6,6 +6,7 @@ import org.nutz.lang.Encoding; import org.nutz.lang.Lang; +import org.nutz.lang.Streams; import org.nutz.lang.util.SimpleContext; public class SocketContext extends SimpleContext { @@ -48,5 +49,12 @@ public void write(String str) { public void writeLine(String str) { write(str + "\n"); } + + public void closeConn() { + if (!atom.socket.isClosed()) { + Streams.safeFlush(atom.ops); + Streams.safeClose(atom.socket); + } + } } diff --git a/src/org/nutz/lang/socket/Sockets.java b/src/org/nutz/lang/socket/Sockets.java index bec0a8489f..a1d6eb0849 100644 --- a/src/org/nutz/lang/socket/Sockets.java +++ b/src/org/nutz/lang/socket/Sockets.java @@ -8,10 +8,7 @@ import java.net.Socket; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; - +import java.util.concurrent.*; import org.nutz.lang.Lang; import org.nutz.lang.Mirror; import org.nutz.lang.Streams; @@ -23,7 +20,7 @@ public abstract class Sockets { private static final Log log = Logs.get(); - + public static void send(String host, int port, InputStream ins, OutputStream ops) { send(host, port, ins, ops, 0); } @@ -150,10 +147,10 @@ public static void localListenByLine(int port, Map actions */ public static void localListenByLine(int port, Map actions, int poolSize) { Sockets.localListenByLine( port, - actions, - Executors.newFixedThreadPool(Runtime.getRuntime() - .availableProcessors() - * poolSize)); + actions, + Executors.newScheduledThreadPool(Runtime.getRuntime() + .availableProcessors() + * poolSize)); } /** @@ -206,8 +203,9 @@ public static void localListen( int port, throw Lang.wrapThrow(e1); } - if (log.isInfoEnabled()) + if (log.isInfoEnabled()) { log.infof("Local socket is up at :%d with %d action ready", port, actions.size()); + } final Context context = Lang.context(); context.set("stop", false); @@ -248,15 +246,17 @@ public void run() { */ while (!context.getBoolean("stop")) { try { - if (log.isDebugEnabled()) + if (log.isDebugEnabled()) { log.debug("Waiting for new socket"); + } Socket socket = server.accept(); if (context.getBoolean("stop")) { Sockets.safeClose(socket); break;// 监护线程也许还是睡觉,还没来得及关掉哦,所以自己检查一下 } - if (log.isDebugEnabled()) + if (log.isDebugEnabled()) { log.debug("accept a new socket, create new SocketAtom to handle it ..."); + } Runnable runnable = (Runnable) borning.born(new Object[]{ context, socket, saTable}); @@ -290,13 +290,15 @@ public void run() { throw e; } finally { - if (log.isInfoEnabled()) + if (log.isInfoEnabled()) { log.info("Stop services ..."); + } service.shutdown(); } - if (log.isInfoEnabled()) + if (log.isInfoEnabled()) { log.infof("Local socket is down for :%d", port); + } } @@ -308,7 +310,7 @@ public void run() { * @return 一定会返回 null */ public static Socket safeClose(Socket socket) { - if (null != socket) + if (null != socket) { try { socket.close(); socket = null; @@ -316,6 +318,7 @@ public static Socket safeClose(Socket socket) { catch (IOException e) { throw Lang.wrapThrow(e); } + } return null; } @@ -326,6 +329,7 @@ public static Socket safeClose(Socket socket) { */ public static SocketAction doClose() { return new SocketAction() { + @Override public void run(SocketContext context) { throw new CloseSocketException(); } diff --git a/src/org/nutz/lang/stream/ComboOutputStream.java b/src/org/nutz/lang/stream/ComboOutputStream.java new file mode 100644 index 0000000000..a3e3672b18 --- /dev/null +++ b/src/org/nutz/lang/stream/ComboOutputStream.java @@ -0,0 +1,65 @@ +package org.nutz.lang.stream; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Collection; + +import org.nutz.lang.Lang; + +/** + * 组合多个输入流,一起写入 + * + * @author zozoh(zozohtnt@gmail.com) + */ +public class ComboOutputStream extends OutputStream { + + private OutputStream[] opss; + + public ComboOutputStream(OutputStream... opss) { + this.opss = opss; + } + + public ComboOutputStream(Collection opss) { + this.opss = opss.toArray(new OutputStream[opss.size()]); + } + + public void addStream(OutputStream ops) { + this.opss = Lang.arrayLast(this.opss, ops); + } + + @Override + public void write(int b) throws IOException { + for (OutputStream ops : opss) { + ops.write(b); + } + } + + @Override + public void write(byte[] b) throws IOException { + for (OutputStream ops : opss) { + ops.write(b); + } + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + for (OutputStream ops : opss) { + ops.write(b, off, len); + } + } + + @Override + public void flush() throws IOException { + for (OutputStream ops : opss) { + ops.flush(); + } + } + + @Override + public void close() throws IOException { + for (OutputStream ops : opss) { + ops.close(); + } + } + +} diff --git a/src/org/nutz/lang/stream/FileChannelInputStream.java b/src/org/nutz/lang/stream/FileChannelInputStream.java new file mode 100644 index 0000000000..99cb8bde68 --- /dev/null +++ b/src/org/nutz/lang/stream/FileChannelInputStream.java @@ -0,0 +1,111 @@ +package org.nutz.lang.stream; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; + +import org.nutz.lang.Streams; + +public class FileChannelInputStream extends InputStream { + + private FileInputStream ins; + + private FileChannel chan; + + private ByteBuffer buf; + + private int bufLen; + + private int bufPos; + + public FileChannelInputStream(FileInputStream ins) { + this.ins = ins; + this.chan = ins.getChannel(); + } + + public FileChannelInputStream(FileChannel chan) { + this.chan = chan; + } + + /** + *
    +                             size()
    +                               V
    +     [xxxxxxxxxxxxxxxxxxxxxxxxx] chan
    +                 |
    +              position()
    +                 V
    +          [xxxxxx] buf
    +            ^    ^
    +            |    +-- bufLen 
    +          bufPos
    +     * 
    + */ + @Override + public long skip(long n) throws IOException { + long pos = chan.position(); + // 根据缓冲,偏移 + if (null != buf) { + pos = pos - bufLen + bufPos; + // 清空缓冲 + buf = null; + bufLen = 0; + bufPos = 0; + } + // 获得绝对位置 + long pos2 = Math.max(pos + n, 0); + pos2 = Math.min(pos2, chan.size()); + chan.position(pos2); + return pos2 - pos; + } + + @Override + public int read() throws IOException { + // 一个个读,那么先读一点出来 + if (null == buf || bufPos >= bufLen) { + buf = ByteBuffer.allocate(8192); + bufLen = chan.read(buf); + bufPos = 0; + } + return buf.getInt(bufPos++); + } + + @Override + public int read(byte[] b) throws IOException { + return read(b, 0, b.length); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + // 如果之前读过一点,那么先写回去 + if (null != buf && bufPos < bufLen) { + int max = Math.min(len, bufLen - bufPos); + buf.get(b, off, len); + bufPos += max; + // 读完了剩下的,清空临时缓存 + if (bufPos >= bufLen) { + buf = null; + bufPos = 0; + bufLen = 0; + } + return max; + } + ByteBuffer bb = ByteBuffer.wrap(b, off, len); + return chan.read(bb); + } + + @Override + public int available() throws IOException { + int remain = bufLen - bufPos; + return remain + (int) (chan.size() - chan.position()); + } + + @Override + public void close() throws IOException { + Streams.safeClose(chan); + Streams.safeClose(ins); + } + +} diff --git a/src/org/nutz/lang/stream/FileChannelOutputStream.java b/src/org/nutz/lang/stream/FileChannelOutputStream.java new file mode 100644 index 0000000000..0c4c94620b --- /dev/null +++ b/src/org/nutz/lang/stream/FileChannelOutputStream.java @@ -0,0 +1,84 @@ +package org.nutz.lang.stream; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; + +import org.nutz.lang.Streams; + +public class FileChannelOutputStream extends OutputStream { + + private FileOutputStream ops; + + private FileChannel chan; + + private ByteBuffer buf; + + public FileChannelOutputStream(FileOutputStream ops) { + this.ops = ops; + this.chan = ops.getChannel(); + } + + public FileChannelOutputStream(FileChannel chan) { + this.chan = chan; + } + + @Override + public void write(int b) throws IOException { + if (null == buf) { + buf = ByteBuffer.allocate(8192); + // 切换到写模式 + if (buf.isReadOnly()) { + buf.flip(); + } + } + // 如果已经写满了,那么就写入通道 + if (!buf.hasRemaining()) { + flushBuffer(); + } + buf.putInt(b); + } + + private void flushBuffer() throws IOException { + // 切换到读模式 + if (!buf.isReadOnly()) { + buf.flip(); + } + // 写入到通道 + chan.write(buf); + // 清空一下 + buf.clear(); + } + + @Override + public void write(byte[] b) throws IOException { + write(b, 0, b.length); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + if (null != buf && buf.position() > 0) { + this.flushBuffer(); + } + ByteBuffer bb = ByteBuffer.wrap(b, off, len); + chan.write(bb); + } + + @Override + public void flush() throws IOException { + if (null != buf) { + this.flushBuffer(); + } + chan.force(false); + Streams.safeFlush(ops); + } + + @Override + public void close() throws IOException { + Streams.safeClose(chan); + Streams.safeClose(ops); + } + +} diff --git a/src/org/nutz/lang/stream/StringOutputStream.java b/src/org/nutz/lang/stream/StringOutputStream.java index d34083ad75..ade7844d80 100644 --- a/src/org/nutz/lang/stream/StringOutputStream.java +++ b/src/org/nutz/lang/stream/StringOutputStream.java @@ -32,6 +32,20 @@ public void write(int b) throws IOException { baos.write(b); } + @Override + public void write(byte[] b) throws IOException { + if (null == baos) + throw new IOException("Stream is closed"); + baos.write(b); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + if (null == baos) + throw new IOException("Stream is closed"); + baos.write(b, off, len); + } + /** * 使用StringBuilder前,务必调用 */ diff --git a/src/org/nutz/lang/tmpl/StrEleConvertor.java b/src/org/nutz/lang/tmpl/StrEleConvertor.java new file mode 100644 index 0000000000..76772bd253 --- /dev/null +++ b/src/org/nutz/lang/tmpl/StrEleConvertor.java @@ -0,0 +1,7 @@ +package org.nutz.lang.tmpl; + +interface StrEleConvertor { + + String process(String str); + +} diff --git a/src/org/nutz/lang/tmpl/StrFormatConvertor.java b/src/org/nutz/lang/tmpl/StrFormatConvertor.java new file mode 100644 index 0000000000..e5c999aeee --- /dev/null +++ b/src/org/nutz/lang/tmpl/StrFormatConvertor.java @@ -0,0 +1,18 @@ +package org.nutz.lang.tmpl; + +public class StrFormatConvertor implements StrEleConvertor { + + private String fmt; + + StrFormatConvertor(String fmt) { + this.fmt = fmt; + } + + @Override + public String process(String str) { + if (null == fmt) + return str; + return String.format(fmt, str); + } + +} diff --git a/src/org/nutz/lang/tmpl/StrMappingConvertor.java b/src/org/nutz/lang/tmpl/StrMappingConvertor.java new file mode 100644 index 0000000000..880266d952 --- /dev/null +++ b/src/org/nutz/lang/tmpl/StrMappingConvertor.java @@ -0,0 +1,27 @@ +package org.nutz.lang.tmpl; + +import java.util.HashMap; +import java.util.Map; + +import org.nutz.lang.Strings; +import org.nutz.lang.meta.Pair; + +public class StrMappingConvertor implements StrEleConvertor { + + private Map mapping; + + StrMappingConvertor(String input) { + mapping = new HashMap(); + String[] ss = Strings.split(input, false, ','); + for (String s : ss) { + Pair p = Pair.create(s); + mapping.put(p.getName(), p.getValue()); + } + } + + @Override + public String process(String str) { + return Strings.sNull(mapping.get(str), str); + } + +} diff --git a/src/org/nutz/lang/tmpl/StrReplaceConvertor.java b/src/org/nutz/lang/tmpl/StrReplaceConvertor.java new file mode 100644 index 0000000000..408b497b12 --- /dev/null +++ b/src/org/nutz/lang/tmpl/StrReplaceConvertor.java @@ -0,0 +1,24 @@ +package org.nutz.lang.tmpl; + +import org.nutz.lang.Strings; + +class StrReplaceConvertor implements StrEleConvertor { + + private String[] args; + + StrReplaceConvertor(String input) { + args = Strings.split(input, false, ','); + } + + @Override + public String process(String str) { + if (args == null || args.length == 0) { + return str; + } + if (args.length == 1) { + return str.replaceAll(args[0], ""); + } + return str.replaceAll(args[0], args[1]); + } + +} diff --git a/src/org/nutz/lang/tmpl/StrTrimConvertor.java b/src/org/nutz/lang/tmpl/StrTrimConvertor.java new file mode 100644 index 0000000000..71bc590317 --- /dev/null +++ b/src/org/nutz/lang/tmpl/StrTrimConvertor.java @@ -0,0 +1,10 @@ +package org.nutz.lang.tmpl; + +class StrTrimConvertor implements StrEleConvertor { + + @Override + public String process(String str) { + return str.trim(); + } + +} diff --git a/src/org/nutz/lang/tmpl/Tmpl.java b/src/org/nutz/lang/tmpl/Tmpl.java index 7742655635..3677acde3f 100644 --- a/src/org/nutz/lang/tmpl/Tmpl.java +++ b/src/org/nutz/lang/tmpl/Tmpl.java @@ -22,7 +22,7 @@ *
  • double: %f 格式化字符串 *
  • boolean: 否/是 格式化字符串 *
  • date : yyyyMMdd 格式化字符串 - *
  • string: %s 格式化字符串 + *
  • string: %s 格式化字符串。或者 (string::A=Apple,B=Banana,C=Cherry) 表映射数据 *
  • json : cqn 输出一段 JSON 文本,c紧凑,q输出引号,n忽略null *
*

@@ -51,10 +51,14 @@ public class Tmpl { * @see #parse(String, Pattern, int, int) */ public static Tmpl parse(String tmpl) { + if (null == tmpl) + return null; return new Tmpl(tmpl, null, -1, -1, null); } public static Tmpl parsef(String fmt, Object... args) { + if (null == fmt) + return null; return new Tmpl(String.format(fmt, args), null, -1, -1, null); } @@ -83,6 +87,8 @@ public static Tmpl parse(String tmpl, int groupIndex, int escapeIndex, TmplEscapeStr getEscapeStr) { + if (null == tmpl) + return null; return new Tmpl(tmpl, ptn, groupIndex, escapeIndex, getEscapeStr); } @@ -103,6 +109,8 @@ public static Tmpl parse(String tmpl, final String startChar, String leftBrace, String rightBrace) { + if (null == tmpl) + return null; String regex = "((?= 0); _fmt.setIgnoreNull(fmt.indexOf('n') < 0); } + } @Override @@ -27,8 +28,11 @@ protected String _val(Object val) { if (val instanceof CharSequence) { if ("-obj-".equals(val)) return "{}"; + if ("-array-".equals(val)) + return "[]"; String s = Strings.trim(val.toString()); - if (Strings.isQuoteBy(s, '[', ']')) + // 直接就是数组或者对象 + if (Strings.isQuoteBy(s, '[', ']') || Strings.isQuoteBy(s, '{', '}')) return s; // zozoh 字符串还是应该转 JSON 吧 // return val.toString(); diff --git a/src/org/nutz/lang/tmpl/TmplStringEle.java b/src/org/nutz/lang/tmpl/TmplStringEle.java index 2562bb19f7..8aca158a49 100644 --- a/src/org/nutz/lang/tmpl/TmplStringEle.java +++ b/src/org/nutz/lang/tmpl/TmplStringEle.java @@ -1,31 +1,87 @@ package org.nutz.lang.tmpl; +import java.util.ArrayList; import java.util.Collection; - -import org.nutz.castor.Castors; +import java.util.List; import org.nutz.lang.Lang; import org.nutz.lang.Strings; +/** + * 格式如下(有点复杂 ^_^!) + * + *

+ * ${path<:@处理器;@处理器'参数1','参数2';@处理器'参数1';:映射>}
+ * 
+ * + * 例如 + * + *
+ * ${path<:@trim;@replace'/','-';@replace'~';:0=A,1=B>}
+ * 
+ * + * @author zozoh(zozohtnt@gmail.com) + */ class TmplStringEle extends TmplDynamicEle { + private List convertors; + public TmplStringEle(String key, String fmt, String dft) { super(null, key, null, dft); this.fmt = Strings.sNull(fmt, null); + parseFormat(this.fmt); + } + + public void parseFormat(String fmt) { + if (null == fmt) { + convertors = null; + return; + } + + // 先拆分处理器 + String[] ss = Strings.split(this.fmt, true, ';'); + + // 预处理字段 + this.convertors = new ArrayList(ss.length); + for (String s : ss) { + // 截取空白 + if (s.equals("@trim")) { + convertors.add(new StrTrimConvertor()); + } + // 字符串替换 + else if (s.startsWith("@replace")) { + String input = s.substring("@replace".length()); + convertors.add(new StrReplaceConvertor(input)); + } + // 字符串映射 + else if (s.startsWith(":")) { + String input = s.substring(1); + convertors.add(new StrMappingConvertor(input)); + } + // 默认是字符串格式化 + else { + convertors.add(new StrFormatConvertor(s)); + } + } } @Override protected String _val(Object val) { + if (null == val) { + return null; + } if (null != val) { if (val.getClass().isArray()) { return Lang.concat(", ", (Object[]) val).toString(); } if (val instanceof Collection) { - return Strings.join(", ", (Collection)val); + return Strings.join(", ", (Collection) val); } } - String re = Castors.me().castTo(val, String.class); - if (!Strings.isBlank(this.fmt)) { - return String.format(fmt, re); + String re = val.toString(); + if (null != convertors && !convertors.isEmpty()) { + for (StrEleConvertor co : convertors) { + re = co.process(re); + } } return re; } diff --git a/src/org/nutz/lang/util/CmdParams.java b/src/org/nutz/lang/util/CmdParams.java index 5fdcd64ad6..204cb68a70 100644 --- a/src/org/nutz/lang/util/CmdParams.java +++ b/src/org/nutz/lang/util/CmdParams.java @@ -8,7 +8,6 @@ import org.nutz.lang.Lang; import org.nutz.lang.Strings; -import org.nutz.lang.util.NutMap; /** * 解析命令参数 diff --git a/src/org/nutz/lang/util/CronSequenceGenerator.java b/src/org/nutz/lang/util/CronSequenceGenerator.java index 8d431a56c9..ac5fa4fbb7 100644 --- a/src/org/nutz/lang/util/CronSequenceGenerator.java +++ b/src/org/nutz/lang/util/CronSequenceGenerator.java @@ -10,6 +10,7 @@ import java.util.GregorianCalendar; import java.util.List; import java.util.TimeZone; + import org.nutz.lang.Strings; diff --git a/src/org/nutz/lang/util/Disks.java b/src/org/nutz/lang/util/Disks.java index 8d9b3f3934..34a1020702 100644 --- a/src/org/nutz/lang/util/Disks.java +++ b/src/org/nutz/lang/util/Disks.java @@ -45,6 +45,30 @@ public static int visitFile(File f, FileVisitor fv, FileFilter filter) { return re; } + /** + * 一个 Vistor 模式的目录深层遍历, 包含目录也会返回 + * + * @param f + * 要遍历的目录或者文件,如果是目录,深层遍历,否则,只访问一次文件 + * @param fv + * 对文件要进行的操作 + * @param filter + * 遍历目录时,哪些文件应该被忽略 + * @return 遍历的文件(目录)个数 + */ + public static int visitFileWithDir(File f, FileVisitor fv, FileFilter filter) { + int re = 0; + fv.visit(f); + re++; + if (f.isDirectory()) { + File[] fs = null == filter ? f.listFiles() : f.listFiles(filter); + if (fs != null) + for (File theFile : fs) + re += visitFileWithDir(theFile, fv, filter); + } + return re; + } + /** * 将两个文件对象比较,得出相对路径 * @@ -93,7 +117,7 @@ public static String getRelativePath(String base, String path) { */ public static String getRelativePath(String base, String path, String equalPath) { // 如果两个路径相等 - if (base.equals(path)) { + if (base.equals(path) || "./".equals(path) || ".".equals(path)) { return equalPath; } @@ -117,7 +141,10 @@ public static String getRelativePath(String base, String path, String equalPath) dir = 0; StringBuilder sb = new StringBuilder(Strings.dup("../", bb.length - pos - dir)); - return sb.append(Lang.concat(pos, ff.length - pos, '/', ff)).toString(); + sb.append(Lang.concat(pos, ff.length - pos, '/', ff)); + if (path.endsWith("/")) + sb.append('/'); + return sb.toString(); } /** @@ -184,9 +211,13 @@ public static String getCanonicalPath(String path) { paths.add(s); } } - if (path.charAt(0) == '/') - return Lang.concat("/", paths).insert(0, '/').toString(); - return Lang.concat("/", paths).toString(); + + StringBuilder sb = Lang.concat("/", paths); + if (path.startsWith("/")) + sb.insert(0, '/'); + if (path.endsWith("/")) + sb.append('/'); + return sb.toString(); } /** @@ -303,12 +334,52 @@ public static final void visitFile(String path, if (null == d) return; visitFile(d, new FileVisitor() { + @Override public void visit(File f) { if (f.isDirectory()) return; fv.visit(f); } }, new FileFilter() { + @Override + public boolean accept(File f) { + if (f.isDirectory()) + return deep; + if (f.isHidden()) + return false; + if (Strings.isEmpty(regex)) + return true; + return Regex.match(regex, f.getName()); + } + }); + } + + /** + * 遍历文件夹下以特定后缀结尾的文件与文件夹 不包括.开头的文件 + * + * @param path + * 根路径 + * @param regex + * 文件名的正则表达式 + * @param deep + * 是否深层遍历 + * @param fv + * 你所提供的访问器,当然就是你自己的逻辑咯 + */ + public static final void visitFileWithDir(String path, + final String regex, + final boolean deep, + final FileVisitor fv) { + File d = Files.findFile(path); + if (null == d) + return; + visitFileWithDir(d, new FileVisitor() { + @Override + public void visit(File f) { + fv.visit(f); + } + }, new FileFilter() { + @Override public boolean accept(File f) { if (f.isDirectory()) return deep; diff --git a/src/org/nutz/lang/util/FloatRange.java b/src/org/nutz/lang/util/FloatRange.java index 42165a33bd..fabd2baec3 100644 --- a/src/org/nutz/lang/util/FloatRange.java +++ b/src/org/nutz/lang/util/FloatRange.java @@ -2,6 +2,8 @@ import org.nutz.lang.Strings; +import java.math.BigDecimal; + public class FloatRange { public static FloatRange make(String s) { @@ -9,14 +11,16 @@ public static FloatRange make(String s) { int i = 0; for (; i < cs.length; i++) { char c = cs[i]; - if (c == ',' || c == ':') + if (c == ',' || c == ':') { break; + } } - if (i == cs.length) + if (i == cs.length) { return make(Float.parseFloat(new String(cs))); + } float left = Float.parseFloat(String.valueOf(cs, 0, i)); - + return make(left, Float.parseFloat(String.valueOf(cs, ++i, cs.length - i))); } @@ -41,7 +45,7 @@ public boolean in(float n) { } public boolean on(float n) { - return n == left || n == right; + return new BigDecimal(n).equals(new BigDecimal(left) ) || new BigDecimal(n).equals(new BigDecimal(right)); } public boolean inon(float n) { @@ -88,6 +92,7 @@ public void setRight(float right) { this.right = right; } + @Override public String toString() { return String.format("%s:%s", left, right); } diff --git a/src/org/nutz/lang/util/HtmlToken.java b/src/org/nutz/lang/util/HtmlToken.java index b403700727..e0dcca72f6 100644 --- a/src/org/nutz/lang/util/HtmlToken.java +++ b/src/org/nutz/lang/util/HtmlToken.java @@ -16,6 +16,8 @@ public class HtmlToken { private List> attributes; public String getTagName() { + if (null == name) + return null; return name.toUpperCase(); } diff --git a/src/org/nutz/lang/util/LinkedArray.java b/src/org/nutz/lang/util/LinkedArray.java index 2111d695a5..9d1321817d 100644 --- a/src/org/nutz/lang/util/LinkedArray.java +++ b/src/org/nutz/lang/util/LinkedArray.java @@ -1,5 +1,6 @@ package org.nutz.lang.util; +import java.io.Serializable; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Iterator; @@ -8,7 +9,9 @@ import org.nutz.json.Json; import org.nutz.lang.Lang; -public class LinkedArray { +public class LinkedArray implements Serializable { + + private static final long serialVersionUID = 1L; public LinkedArray() { this(256); diff --git a/src/org/nutz/lang/util/LinkedByteBuffer.java b/src/org/nutz/lang/util/LinkedByteBuffer.java new file mode 100644 index 0000000000..a872163a70 --- /dev/null +++ b/src/org/nutz/lang/util/LinkedByteBuffer.java @@ -0,0 +1,644 @@ +package org.nutz.lang.util; + +import java.io.IOException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; + +import org.nutz.lang.Encoding; +import org.nutz.lang.Lang; + +/** + * 可自动延申的字节数组,内部有两个游标(只读游标cursor)和(只写游标last) + *

+ * 这个类设计给有一小块缓冲数据,可能需要同时读写的场景。 + *

+ * !!! 注意,本类非线程安全,如果多线程共享实例,请自行加锁保护。 + * + *

+ * 实际上的存储 ...
+ * |<---- unit --->|
+ * |               | -----
+ * [0d 0a 18 23 ...]  ^
+ * [35 f2 25 0e ...]  nb
+ * [12 ae 11 01 ...]  V
+ * ...               <- 如果满了,再写会自动延申
+ * 
+ * 逻辑上可以看作一个数组
+ * 
+ *    rIndex(读)                                             capacity 已分配容量
+ *      V                            V
+ * [0d 0a 18 23 35 f2 25 0e .  .  .  ]
+ *              ^            ^
+ *           wIndex       limit 有效区的位置
+ *            只写下标
+ *      如果指向 capacity则无空间
+ * 
+ * + * + * @author zozoh(zozohtnt@gmail.com) + */ +public class LinkedByteBuffer { + + // 为了防止内存爆掉,这里指定一个上限吧 + private int maxCapacity; + + /** + * 已加载的缓冲行字节总数 + */ + private int capacity; + + /** + * 表示有效区的长度 + */ + private int limit; + + /** + * 只读游标 + */ + private int rIndex; + + /** + * 只写游标 + */ + private int wIndex; + /** + * 动态增长内存时,需要分配的最小字节单位 + */ + private int unit; + + /** + * 缓冲(字节数组列表) + */ + private ArrayList list; + + /** + * 创建一个 10M上限实例,8K增量单位,默认分配 80K的实例 + */ + public LinkedByteBuffer() { + this(8192, 10, 1024 * 10240); + } + + /** + * 创建一个 10M上限实例,8K增量单位的实例 + * + * @param nb + * 初始分配多少单位的字节 + */ + public LinkedByteBuffer(int nb) { + this(8192, nb, 1024 * 10240); + } + + /** + * 创建一个 10M上限实例 + * + * @param unit + * 动态分配字节的最小单位,譬如 8192(8K) + * @param nb + * 初始分配多少单位的字节 + */ + public LinkedByteBuffer(int unit, int nb) { + this(unit, nb, 1024 * 10240); + } + + /** + * @param unit + * 动态分配字节的最小单位,譬如 8192(8K) + * @param nb + * 初始分配多少单位的字节 + * @param maxLimit + * 最大可分配的字节数,譬如 1024*10240(10M) + */ + public LinkedByteBuffer(int unit, int nb, int maxLimit) { + this.unit = unit; + this.capacity = unit * nb; + this.limit = 0; + this.rIndex = 0; + this.wIndex = 0; + + // 默认上限给 10M + this.maxCapacity = maxLimit; + + // 分配内存 + list = new ArrayList(nb); + for (int i = 0; i < nb; i++) { + list.add(new byte[unit]); + } + } + + /** + * 将只读和只写游标同时归0; 并清空有效区 + */ + public void reset() { + limit = 0; + rIndex = 0; + wIndex = 0; + } + + /** + * 从当前位置偏移只读游标。但是它也遵循 {@link #seekRead(int)} 函数的限定。 + * + * @param off + * 偏移量 + * @return 偏移后的只读游标位置 + */ + public int skipRead(int off) { + return this.seekRead(rIndex + off); + } + + /** + * 移动只写游标。你给入的新位置不能小于0,也不能大过有效区上限。 + *

+ * 否则,会自动对齐到两端边界。即,小于0会当作0,大于有效区上限则等于上限。 + * + * @param pos + * 新的只读游标位置 + * @return 移动后的只读游标位置 + */ + public int seekRead(int pos) { + this.rIndex = Math.max(0, Math.min(pos, limit)); + return this.rIndex; + } + + /** + * 从当前位置偏移只写游标。但是它也遵循 {@link #seekWrite(int)} 函数的限定。 + * + * @param off + * 偏移量 + * @return 偏移后的只写游标位置 + */ + public int skipWrite(int off) { + return this.seekWrite(wIndex + off); + } + + /** + * 移动只写游标。你给入的新位置不能小于0,也不能大过【有效区】上限。 + *

+ * 否则,会自动对齐到两端边界。即,小于0会当作0,大于上限则等于上限。 + * + * @param pos + * 新的只写游标位置 + * @return 移动后的只写游标位置 + */ + public int seekWrite(int pos) { + this.wIndex = Math.max(0, Math.min(pos, limit)); + return this.wIndex; + } + + /** + * 将自身的内容,从当前位置(内部只写游标)写入输入数组的内容 + * + * @param buf + * 输入数组 + * + * @throws IOException + * 当写入的字节超过自己的指定最大限度 + */ + public void write(byte[] buf) throws IOException { + write(buf, 0, buf.length); + } + + /** + * 将自身的内容,从当前位置(内部只写游标)写入输入数组的内容 + * + * @param buf + * 输入数组 + * @param off + * 要写入字节的起始位置 + * @param len + * 要写入多少字节 + * + * @throws IOException + * 当写入的字节超过自己的指定最大限度 + */ + public void write(byte[] buf, int off, int len) throws IOException { + // 超过上限了,不能写了 + if (capacity + len > this.maxCapacity) { + throw new IOException("Output of MaxLimitation " + this.maxCapacity); + } + + // 还是有多少空间是可写的? + int remain = this.capacity - this.wIndex; + + // 超过了,那么还需要分配多少行数据 + if (len > remain) { + int space = len - remain; + int n = space / unit; + if (n * unit < space) { + n += 1; + } + // 分配 + for (int i = 0; i < n; i++) { + list.add(new byte[unit]); + } + capacity = list.size() * unit; + } + + // 开始点 + int row = wIndex / unit; // 从第几行开始 + int col = wIndex - row * unit; // 从第几列开始 + + // 对其的开始(即,如果不从行首 copy,那么对齐到行首,以便计算行数 + int padLen = len + col; + + // 一共 要完整写几行 + int r_count = padLen / unit; // 先需要完整写的行数 + + // 准备裸行 + byte[] tag = list.get(row); + + // 写第一行 + int x = off; // 目标数组要 copy 的起始位置 + int n = Math.min(unit - col, len); // 要 copy 的字节数 + int c = 0; // 一共 copy 完的字节数 + System.arraycopy(buf, x, tag, col, n); + c += n; + x += n; + + // 写整行 + int i = 1; // 行从 row 偏移的下标 + for (; i < r_count; i++) { + tag = list.get(row + i); + System.arraycopy(buf, x, tag, 0, unit); + c += unit; + x += unit; + } + + // 写最后一行 + if (c < len) { + tag = list.get(row + i); + n = len - c; + System.arraycopy(buf, x, tag, 0, n); + } + + // 最后移动只写游标 + this.wIndex += len; + this.limit = Math.max(this.limit, this.wIndex); + } + + /** + * 写入字符串 + * + * @param str + * 字符串 + * + * @throws IOException + * 当写入的字节超过自己的指定最大限度 + */ + public void write(String str) throws IOException { + byte[] buf = str.getBytes(Encoding.CHARSET_UTF8); + this.write(buf); + } + + /** + * 写入一行字符串,会自动再后面添加换行符. + * + * @param str + * 字符串(UTF-8编码) + * @throws IOException + * 当写入的字节超过自己的指定最大限度 + */ + public void writeLine(String str) throws IOException { + byte[] buf = str.getBytes(Encoding.CHARSET_UTF8); + byte[] nwl = System.lineSeparator().getBytes(); + this.write(buf); + this.write(nwl); + } + + /** + * 将自身的内容,从当前位置(内部游标)copy 到目标数组,并会将游标指向下一个未读取的位置 + * + * @param buf + * 目标数组 + * @return 一共实际 copy 的字节数。 -1 表示不在有字节可以被 copy 了 + * + * @see #read(byte[], int, int) + */ + public int read(byte[] buf) { + return read(buf, 0, buf.length); + } + + /** + * 将自身的内容,从当前位置(内部只读游标)copy 到目标数组,并会将只读游标指向下一个未读取的位置 + * + * @param buf + * 目标数组 + * @param off + * 起始位置下标 + * @param len + * 最多 copy 多少字节 + * @return 一共实际 copy 的字节数。 -1 表示不在有字节可以被 copy 了 + */ + public int read(byte[] buf, int off, int len) { + int remain = limit - rIndex; + if (remain <= 0) { + return -1; + } + len = Math.min(len, remain); + if (0 >= len) { + return len; + } + + // 开始点 + int row = rIndex / unit; // 从第几行开始 + int col = rIndex - row * unit; // 从第几列开始 + + // 对其的开始(即,如果不从行首 copy,那么对齐到行首,以便计算行数 + int padLen = len + col; + + // 一共要完整 copy 几行 + int r_count = padLen / unit; // 先需要 copy 的整行 + + // 准备源 + byte[] src = list.get(row); + + // Copy 第一行 + int x = off; // 目标数组要 copy 的起始位置 + int n = Math.min(unit - col, len); // 要 copy 的字节数 + int c = 0; // 一共 copy 完的字节数 + System.arraycopy(src, col, buf, x, n); + c += n; + x += n; + + // Copy 其余整行 + int i = 1; // 行从 cursor 偏移的下标 + for (; i < r_count; i++) { + src = list.get(row + i); + System.arraycopy(src, 0, buf, x, unit); + c += unit; + x += unit; + } + + // Copy 最后一行 + if (c < len) { + src = list.get(row + i); + n = len - c; + System.arraycopy(src, col, buf, x, n); + } + + // 最后移动只读游标 + this.rIndex += len; + + // 返回实际读取的字节数 + return len; + } + + /** + * 读取一行的字符串(UTF-8编码) + * + * @return 从当前只读位置到遇到的第一个换行符之间的字符串(UTF-8编码)。
+ * null 表示已经没有可读的内容了 + */ + public String readLine() { + // 木有行了 + if (rIndex >= limit) + return null; + + // 开始点 + int row = rIndex / unit; // 从第几行开始 + int col = rIndex - row * unit; // 从第几列开始 + + // 最多寻找的字符数 + int max = limit - rIndex; + + // 试图寻找到下一个 '\n' + int count = 0; + boolean found = false; + for (; row < list.size(); row++) { + byte[] bs = list.get(row); + for (; col < unit; col++) { + byte b = bs[col]; + count++; // 计数 + if (b == '\n' || count >= max) { + found = true; + break; + } + } + if (found) + break; + else + col = 0; + } + + // 读取字符串 + byte[] buf = new byte[count]; + this.read(buf); + + // 去掉结尾的 -n + int i = buf.length - 1; + if (buf[i] == '\n') + i--; + if (buf[i] == '\r') + i--; + if (buf[i] != '\n') + i++; + + // 返回字符串 + return new String(buf, 0, i, Encoding.CHARSET_UTF8); + } + + /** + * 读取剩下的全部字符串(UTF-8编码) + * + * @return 从当前只读位置到有效区结尾全部的内容,并转成字符串(UTF-8编码)。
+ * null 表示没有可读的内容了 + */ + public String readAll() { + int len = limit - rIndex; + if (len <= 0) { + return null; + } + byte[] buf = new byte[len]; + this.read(buf); + + // 返回字符串 + return new String(buf, Encoding.CHARSET_UTF8); + } + + /** + * 从指定位置读取一个字节 + * + * @param index + * 下标。如果为负数,则表示从后面读取 + * @return 字节 + */ + public byte get(int index) { + if (this.isEmpty()) { + return -1; + } + // 从后面数 + if (index < 0) { + index = Math.max(0, limit + index); + } + // 从前面数 + else { + index = Math.min(index, limit - 1); + } + int row = index / unit; // 从第几行开始 + int col = index - row * unit; // 从第几列开始 + return this.list.get(row)[col]; + } + + /** + * 向指定从指定位置写入一个字节 + * + * @param index + * 下标。如果为负数,则表示从后面读取 + * @return 字节 + */ + public void set(int index, int b) { + if (this.isEmpty()) { + return; + } + // 从后面数 + if (index < 0) { + index = Math.max(0, limit + index); + } + // 从前面数 + else { + index = Math.min(index, limit - 1); + } + int row = index / unit; // 从第几行开始 + int col = index - row * unit; // 从第几列开始 + this.list.get(row)[col] = (byte) b; + } + + /** + * @return 是否内容空空如也 + */ + public boolean isEmpty() { + return this.limit <= 0; + } + + /** + * @return 本实例能增长的上限。默认为 10M + */ + public int getMaxCapacity() { + return maxCapacity; + } + + /** + * @return 已经分配的可写字节总数 + */ + public int getCapacity() { + return capacity; + } + + /** + * @return 有效区大小 + */ + public int getLimit() { + return limit; + } + + /** + * 截取内容。给定长度必须要在有效区长度范围以内 + * + * @param limit + * 长度 + * @return 截取后的内容大小 + */ + public int truncate(int limit) { + this.limit = Math.min(this.limit, limit); + return this.limit; + } + + /** + * @return 只写游标 + */ + public int getWriteIndex() { + return wIndex; + } + + /** + * @return 只读游标 + */ + public int getReadIndex() { + return rIndex; + } + + /** + * @return 每次增长的最小字节数单位 + */ + public int getUnit() { + return unit; + } + + /** + * @return 内容有效区的 SHA1 签名 + */ + public String sha1sum() { + try { + return digest("SHA1"); + } + catch (NoSuchAlgorithmException e) { + throw Lang.wrapThrow(e); + } + } + + /** + * @return 内容有效区的 MD5 签名 + */ + public String md5sum() { + try { + return digest("MD5"); + } + catch (NoSuchAlgorithmException e) { + throw Lang.wrapThrow(e); + } + } + + /** + * 计算内容有效区的 的数字签名 + * + * @param algorithm + * 算法,比如 "SHA1" 或者 "MD5" 等 + * @return 数字签名 + * @throws NoSuchAlgorithmException + * 数据签名方法不支持 + */ + public String digest(String algorithm) throws NoSuchAlgorithmException { + MessageDigest md = MessageDigest.getInstance(algorithm); + + // 更新的整行 + int count = limit / unit; + int row = 0; + for (; row < count; row++) { + byte[] buf = this.list.get(row); + md.update(buf); + } + + // 更新最后一行 + int n = limit - count * unit; + if (n > 0) { + byte[] buf = this.list.get(row); + md.update(buf, 0, n); + } + + // 计算签名 + byte[] hashBytes = md.digest(); + return Lang.fixedHexString(hashBytes); + + } + + public byte[] toArray() { + byte[] re = new byte[limit]; + for (int i = 0; i < list.size(); i++) { + int from = i * unit; + int to = Math.min(limit, from + unit); + if (to <= from) { + break; + } + int len = to - from; + byte[] src = list.get(i); + System.arraycopy(src, 0, re, from, len); + } + return re; + } + + public String toString() { + byte[] bs = this.toArray(); + return new String(bs, Encoding.CHARSET_UTF8); + } + +} diff --git a/src/org/nutz/lang/util/LinkedCharArray.java b/src/org/nutz/lang/util/LinkedCharArray.java index 818a5b8044..40439fcb6b 100644 --- a/src/org/nutz/lang/util/LinkedCharArray.java +++ b/src/org/nutz/lang/util/LinkedCharArray.java @@ -1,11 +1,14 @@ package org.nutz.lang.util; +import java.io.Serializable; import java.util.ArrayList; import org.nutz.lang.Lang; import org.nutz.lang.Strings; -public class LinkedCharArray { +public class LinkedCharArray implements Serializable { + + private static final long serialVersionUID = 1L; public LinkedCharArray() { this(256); @@ -98,10 +101,7 @@ public LinkedCharArray set(int index, char e) { private void checkBound(int index) { if (index >= size() || index < 0) - throw new IndexOutOfBoundsException("Index: " - + index - + ", Size: " - + size()); + throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size()); } public LinkedCharArray clear() { diff --git a/src/org/nutz/lang/util/LinkedIntArray.java b/src/org/nutz/lang/util/LinkedIntArray.java index 1dce584e3c..3e9ba2bf62 100644 --- a/src/org/nutz/lang/util/LinkedIntArray.java +++ b/src/org/nutz/lang/util/LinkedIntArray.java @@ -1,13 +1,16 @@ package org.nutz.lang.util; +import java.io.Serializable; import java.util.ArrayList; import org.nutz.json.Json; import org.nutz.lang.Lang; -public class LinkedIntArray { +public class LinkedIntArray implements Serializable { - public LinkedIntArray() { + private static final long serialVersionUID = 1L; + + public LinkedIntArray() { this(256); } @@ -79,10 +82,7 @@ public LinkedIntArray setLast(int e) { private void checkBound(int index) { if (index >= size() || index < 0) - throw new IndexOutOfBoundsException("Index: " - + index - + ", Size: " - + size()); + throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size()); } public LinkedIntArray clear() { diff --git a/src/org/nutz/lang/util/LinkedLongArray.java b/src/org/nutz/lang/util/LinkedLongArray.java index 6511861f45..0b726b0d5a 100644 --- a/src/org/nutz/lang/util/LinkedLongArray.java +++ b/src/org/nutz/lang/util/LinkedLongArray.java @@ -1,11 +1,15 @@ package org.nutz.lang.util; +import java.io.Serializable; import java.util.ArrayList; import org.nutz.json.Json; import org.nutz.lang.Lang; -public class LinkedLongArray { +public class LinkedLongArray implements Serializable { + + private static final long serialVersionUID = 1L; + public LinkedLongArray() { this(256); } diff --git a/src/org/nutz/lang/util/MultiLineProperties.java b/src/org/nutz/lang/util/MultiLineProperties.java index 03a0c2176b..6baa6e9f74 100644 --- a/src/org/nutz/lang/util/MultiLineProperties.java +++ b/src/org/nutz/lang/util/MultiLineProperties.java @@ -8,10 +8,10 @@ import java.io.Writer; import java.util.ArrayList; import java.util.Collection; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import org.nutz.lang.Encoding; import org.nutz.lang.Strings; @@ -31,7 +31,7 @@ public MultiLineProperties(Reader reader) throws IOException { } public MultiLineProperties() { - maps = new LinkedHashMap(); + maps = new ConcurrentHashMap(); } protected Map maps; @@ -104,12 +104,12 @@ public synchronized void load(Reader reader, boolean clear) throws IOException { if (null == ss) return; } else { - maps.put(Strings.trim(s), null); + maps.put(Strings.trim(s), ""); } } } - public synchronized void clear() { + public void clear() { maps.clear(); } @@ -152,11 +152,11 @@ public synchronized String put(String key, String value) { } @SuppressWarnings({"unchecked", "rawtypes"}) - public synchronized void putAll(Map t) { + public void putAll(Map t) { maps.putAll(t); } - public synchronized String remove(Object key) { + public String remove(Object key) { return maps.remove(key); } diff --git a/src/org/nutz/lang/util/NutBean.java b/src/org/nutz/lang/util/NutBean.java index 8f145c0287..be4ad7b0aa 100644 --- a/src/org/nutz/lang/util/NutBean.java +++ b/src/org/nutz/lang/util/NutBean.java @@ -104,6 +104,8 @@ public interface NutBean extends Map { NutBean addv(String key, Object value); NutBean addv2(String key, Object value); + + NutBean addv3(String key, Object value); NutBean setv(String key, Object value); @@ -111,6 +113,8 @@ public interface NutBean extends Map { NutBean setAll(Map map); + Object getFallback(String... keys); + /** * 从 Map 里挑选一些键生成一个新的 Map * @@ -160,6 +164,17 @@ public interface NutBean extends Map { */ NutBean pickAndRemoveBy(Pattern p, boolean isNot); + /** + * 就是 pickAndRemoveBy 的一个便利写法 + * + * @param regex + * 正则表达式,! 开头表示取反 + * @return 新 Map + * + * @see #pickAndRemoveBy(Pattern, boolean) + */ + NutMap pickAndRemoveBy(String regex); + /** * 从 Map 里将指定的键过滤,生成一个新的 Map * @@ -222,6 +237,23 @@ public interface NutBean extends Map { */ NutBean setnxAll(Map map); + /** + * @param key + * 键,支持用 | 分隔,如果一个值为空,则继续寻找下一个候选键。 其中,支持 "." + * 作为层级路径分隔 + * @param dft + * 都没有找到时返回的默认值 + * @return 值 + */ + Object getOr(String key, Object dft); + + Object getOrBy(String[] keys, Object dft); + + /** + * @see #getOr(String, Object) + */ + Object getOr(String key); + /** * 获取对应的值,若不存在,用factory创建一个,然后设置进去,返回之 * diff --git a/src/org/nutz/lang/util/NutMap.java b/src/org/nutz/lang/util/NutMap.java index e18c36039e..caea3137ca 100644 --- a/src/org/nutz/lang/util/NutMap.java +++ b/src/org/nutz/lang/util/NutMap.java @@ -8,9 +8,10 @@ import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; +import java.util.ListIterator; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; +import java.util.regex.Matcher; import java.util.regex.Pattern; import org.nutz.castor.Castors; @@ -20,6 +21,7 @@ import org.nutz.lang.Mirror; import org.nutz.lang.Strings; import org.nutz.lang.born.Borning; +import org.nutz.mapl.Mapl; /** * 对于 LinkedHashMap 的一个友好封装 @@ -28,9 +30,13 @@ * * @author zozoh(zozohtnt@gmail.com) */ -@SuppressWarnings("serial") public class NutMap extends LinkedHashMap implements NutBean { + /** + * + */ + private static final long serialVersionUID = 1L; + public static NutMap WRAP(Map map) { if (null == map) return null; @@ -104,6 +110,42 @@ public NutMap duplicate() { return map; } + public Object getFallback(String... keys) { + for (String key : keys) { + Object val = this.get(key); + if (null != val) { + return val; + } + } + return null; + } + + @Override + public Object getOr(String key) { + return getOr(key, null); + } + + @Override + public Object getOr(String key, Object dft) { + if (Strings.isBlank(key)) + return null; + String[] ks = Strings.splitIgnoreBlank(key, "[|]"); + return this.getOrBy(ks, dft); + } + + @Override + public Object getOrBy(String[] keys, Object dft) { + if (null == keys || keys.length == 0) + return null; + for (String k : keys) { + Object v = Mapl.cell(this, k); + if (null != v) { + return v; + } + } + return dft; + } + /** * 从 Map 里挑选一些键生成一个新的 Map * @@ -116,11 +158,10 @@ public NutMap pick(String... keys) { if (keys.length == 0) return new NutMap(); NutMap re = new NutMap(); - for (Map.Entry en : this.entrySet()) { - String key = en.getKey(); - if (Lang.contains(keys, key)) { - re.put(key, en.getValue()); - } + for (String key : keys) { + Object val = this.get(key); + if (null != val) + re.put(key, val); } return re; } @@ -156,7 +197,7 @@ public NutMap pickBy(String regex) { if (Strings.isBlank(regex)) return this.duplicate(); boolean isNot = regex.startsWith("!"); - Pattern p = Pattern.compile(isNot ? regex.substring(1) : regex); + Pattern p = Regex.getPattern(isNot ? regex.substring(1) : regex); return pickBy(p, isNot); } @@ -203,6 +244,7 @@ public NutMap pickBy(Pattern p, boolean isNot) { * * @see #pickAndRemoveBy(Pattern, boolean) */ + @Override public NutMap pickAndRemoveBy(String regex) { if (Strings.isBlank(regex)) return new NutMap(); @@ -330,11 +372,17 @@ public Collection values() { } @Override - public Set> entrySet() { + public Set> entrySet() { if (null == _map) return super.entrySet(); - HashSet> vals = new HashSet>(); + + HashSet> vals = new HashSet>(); vals.addAll(_map.entrySet()); + for (Map.Entry entry : _map.entrySet()) { + // 如果本身也有这个entry,attach 上来的那个就要删除掉 + if (super.containsKey(entry.getKey())) + vals.remove(entry); + } vals.addAll(super.entrySet()); return vals; } @@ -346,15 +394,15 @@ public void clear() { _map.clear(); } - private NutMap _map; + private Map _map; - public NutMap attach(NutMap map) { + public NutMap attach(Map map) { _map = map; return this; } - public NutMap detach() { - NutMap re = _map; + public Map detach() { + Map re = _map; _map = null; return re; } @@ -506,7 +554,16 @@ public List getAsList(String key, Class eleType) { Object v = get(key); if (null == v) return null; - return (List) v; + List list = (List) v; + ListIterator it = list.listIterator(); + while (it.hasNext()) { + Object ele = it.next(); + if (null != ele && !eleType.isAssignableFrom(ele.getClass())) { + Object ele2 = Castors.me().castTo(ele, eleType); + it.set(ele2); + } + } + return list; } /** @@ -613,6 +670,38 @@ public NutMap addv2(String key, Object value) { return this; } + /** + * 为 Map 增加一个名值对。强制设置为一个列表,如果有同名则合并。
+ * 值如果是集合或者数组则拆包。 + * + * @param key + * @param value + */ + @Override + @SuppressWarnings("unchecked") + public NutMap addv3(String key, Object value) { + List list = (List) get(key); + if (null == list) { + list = new LinkedList(); + put(key, list); + } + if (null != value) { + if (value instanceof Collection) { + Collection col = (Collection) value; + list.addAll(col); + } else if (value.getClass().isArray()) { + int len = Array.getLength(value); + for (int i = 0; i < len; i++) { + Object ele = Array.get(value, i); + list.add(ele); + } + } else { + list.add(value); + } + } + return this; + } + /** * 向某个键增加一组值,如果原来就有值,是集合的话,会被合并,否则原来的值用列表包裹后再加入新值 * @@ -875,7 +964,14 @@ public boolean match(Map map) { // 其他的值,匹配一下 else { Object val = map.get(key); - if (!__match_val(mtc, val)) { + // 空串"" 表示必须有这个键,且不能为空 + if (Strings.isEmpty(mtc.toString())) { + if (null == val || Strings.isBlank(val.toString())) { + return false; + } + } + // 复杂匹配 + else if (!__match_val(mtc, val)) { return false; } } @@ -884,6 +980,72 @@ public boolean match(Map map) { return true; } + private static final Pattern _M_RG_TP = Regex.getPattern("^(int|float|double|long|time|date)?" + + "([\\[(][^\\[\\]()]+[\\])])$"); + + /** + * 将自己的字段(仅第一层)预编译,即: + *
    + *
  • "^xxx" : 变正则
  • + *
  • "$TYPE[12,43)" : 变区间,类型支持 + * `int|float|double|long|time|date`
  • + *
  • 其他维持不变 + *
+ * 以便可以提高 match 函数执行的效率 + * + * @return 自身以便链式赋值 + */ + public NutMap evalSelfForMatching() { + for (Map.Entry en : this.entrySet()) { + Object val = en.getValue(); + if (null != val && val instanceof String) { + String str = (String) val; + // 正则 + if (str.startsWith("^")) { + en.setValue(Pattern.compile(str)); + continue; + } + // 区间 + Matcher m = _M_RG_TP.matcher(str); + if (m.find()) { + String type = Strings.sBlank(m.group(1), "int"); + String rval = m.group(2); + Region rg = null; + // 整数区间 + if ("int".equals(type)) { + rg = Region.Int(rval); + } + // 长整数区间 + else if ("long".equals(type)) { + rg = Region.Long(rval); + } + // 浮点区间 + else if ("float".equals(type)) { + rg = Region.Float(rval); + } + // 双精度浮点区间 + else if ("double".equals(type)) { + rg = Region.Double(rval); + } + // 日期区间 + else if ("date".equals(type)) { + rg = Region.Date(rval); + } + // 时间区间 + else if ("time".equals(type)) { + rg = Region.Time(rval); + } + // Update + if (null != rg) { + en.setValue(rg); + } + } + } + } + return this; + } + + @SuppressWarnings({"rawtypes", "unchecked"}) private boolean __match_val(final Object mtc, Object val) { Mirror mi = Mirror.me(mtc); @@ -898,7 +1060,7 @@ private boolean __match_val(final Object mtc, Object val) { final String s = mtc.toString(); if (s.startsWith("^")) { - regex = Pattern.compile(s); + regex = Regex.getPattern(s); } // 不是正则表达式,那么精确匹配字符串 else { @@ -947,8 +1109,12 @@ public void invoke(int index, Object ele, int length) { return re[0]; } // 范围的话... - else if (mi.is(Region.class)) { - throw Lang.noImplement(); + else if (mi.isOf(Region.class)) { + if (val instanceof Comparable) { + Comparable cp = (Comparable) val; + Region rg = (Region) mtc; + return rg.match(cp); + } } // 其他的统统为不匹配 return false; @@ -959,7 +1125,12 @@ public Object eval(String el) { } public int evalInt(String el) { - return (Integer) El.eval(Lang.context(this), el); + Object obj = El.eval(Lang.context(this), el); + if (obj == null) + return 0; + if (obj instanceof Number) + return ((Number) obj).intValue(); + return Integer.parseInt(obj.toString()); } /** diff --git a/src/org/nutz/lang/util/NutType.java b/src/org/nutz/lang/util/NutType.java index 000c913fb5..3b89898c2d 100644 --- a/src/org/nutz/lang/util/NutType.java +++ b/src/org/nutz/lang/util/NutType.java @@ -3,6 +3,7 @@ import java.lang.reflect.Array; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import java.util.Arrays; import java.util.List; import java.util.Map; @@ -92,4 +93,38 @@ public void setOwnerType(Type ownerType) { public void setRawType(Type rawType) { this.rawType = rawType; } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(actualTypeArguments); + result = prime * result + ((ownerType == null) ? 0 : ownerType.hashCode()); + result = prime * result + ((rawType == null) ? 0 : rawType.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; + NutType other = (NutType) obj; + if (!Arrays.equals(actualTypeArguments, other.actualTypeArguments)) + return false; + if (ownerType == null) { + if (other.ownerType != null) + return false; + } else if (!ownerType.equals(other.ownerType)) + return false; + if (rawType == null) { + if (other.rawType != null) + return false; + } else if (!rawType.equals(other.rawType)) + return false; + return true; + } } diff --git a/src/org/nutz/lang/util/Regex.java b/src/org/nutz/lang/util/Regex.java new file mode 100644 index 0000000000..fbbf8e0dd7 --- /dev/null +++ b/src/org/nutz/lang/util/Regex.java @@ -0,0 +1,32 @@ +package org.nutz.lang.util; + +import java.util.regex.Pattern; + +import org.nutz.repo.cache.simple.LRUCache; + +public class Regex { + + protected static LRUCache cache = new LRUCache(10000); + + public static void setCacheSize(int size) { + cache.setCacheSize(size); + } + + public static void clear() { + if (cache != null) + cache.clear(); + } + + public static Pattern getPattern(String regex) { + Pattern pattern = cache.get(regex); + if (pattern == null) { + pattern = Pattern.compile(regex); + cache.put(regex, pattern); + } + return pattern; + } + + public static boolean match(String regex, String value) { + return getPattern(regex).matcher(value).find(); + } +} diff --git a/src/org/nutz/lang/util/SimpleContext.java b/src/org/nutz/lang/util/SimpleContext.java index de7de9ec05..b5e3d91efa 100644 --- a/src/org/nutz/lang/util/SimpleContext.java +++ b/src/org/nutz/lang/util/SimpleContext.java @@ -53,6 +53,10 @@ public Context clear() { public Object get(String name) { return map.get(name); } + + public Object remove(String name) { + return map.remove(name); + } public SimpleContext clone() { SimpleContext context = new SimpleContext(); diff --git a/src/org/nutz/lang/util/Tag.java b/src/org/nutz/lang/util/Tag.java index f508fa2714..dc78c71c88 100644 --- a/src/org/nutz/lang/util/Tag.java +++ b/src/org/nutz/lang/util/Tag.java @@ -16,6 +16,17 @@ * @author zozoh(zozohtnt@gmail.com) */ public class Tag extends SimpleNode { + /** + * 标题级别 正则表达式 + */ + private static Pattern NUMBER_PATTERN = Pattern.compile("^H([1-9])$"); + + /** + * 存储一段 HTML 片段,如果这个有值,那么 _join_to_string() 的时候,会直接使用它 TODO zozoh: + * 我知道这是一个丑陋的实现,但是有什么办法,今天晚上就要用啊。我来不及写个 HTML 的解析器 -_-! 以后有机会,应该写个好点的 HTML + * 解析类。Jsoup 那玩意稍微有点弱啊~~~ + */ + private String htmlSegment; public static Tag tag(String name, String... attrs) { return NEW(name).attrs(attrs); @@ -36,6 +47,12 @@ public static Tag text(String text) { return tag; } + public static Tag html(String html) { + Tag tag = new Tag(); + tag.htmlSegment = html; + return tag; + } + public boolean isBlock() { return this.is("^(HEAD|DIV|P|UL|OL|LI|BLOCKQUOTE|PRE|TITLE|H[1-9]|HR|TABLE|TR|TD)$"); } @@ -45,7 +62,7 @@ public boolean isInline() { } public boolean isNoChild() { - return this.is("^(BR|HR|IMG|LINK|META)$"); + return this.is("^(BR|HR|IMG|LINK|META|INPUT)$"); } public boolean isHeading() { @@ -59,9 +76,10 @@ public boolean isHeading() { */ public int getHeadingLevel() { if (this.isElement()) { - Matcher m = Pattern.compile("^H([1-9])$").matcher(tagName()); - if (m.find()) + Matcher m = NUMBER_PATTERN.matcher(tagName()); + if (m.find()) { return Integer.parseInt(m.group(1)); + } } return 0; } @@ -71,11 +89,13 @@ public boolean isList() { } public boolean is(String regex) { - if (this.isTextNode()) - return false; String tagName = this.tagName(); - if (regex.startsWith("^")) + if (null == tagName) { + return false; + } + if (regex.startsWith("^")) { return tagName.matches(regex.toUpperCase()); + } return tagName.equals(regex.toUpperCase()); } @@ -88,19 +108,28 @@ public boolean isBody() { } public boolean isElement() { + if (null != htmlSegment) { + return true; + } return this.get().isElement(); } public boolean isTextNode() { + if (null != htmlSegment) { + return false; + } return this.get().isText(); } public boolean isChildAllInline() { - if (!get().isElement()) + if (!get().isElement()) { return false; - for (Node ht : this.getChildren()) - if (((Tag) ht).isBlock()) + } + for (Node ht : this.getChildren()) { + if (((Tag) ht).isBlock()) { return false; + } + } return true; } @@ -118,6 +147,15 @@ public String name() { } public String tagName() { + if (null != this.htmlSegment) { + if (this.htmlSegment.startsWith("<")) { + int pos = this.htmlSegment.indexOf(' '); + if (pos > 1) { + return this.htmlSegment.substring(1, pos); + } + } + return null; + } return get().getTagName(); } @@ -171,8 +209,9 @@ public Tag addClass(String name) { public boolean hasClass(String name) { String cns = get().getAttrVal("class"); - if (null == cns || cns.length() < name.length()) + if (null == cns || cns.length() < name.length()) { return false; + } return (" " + cns + " ").indexOf(" " + name + " ") != -1; } @@ -192,7 +231,11 @@ public String id() { } public String getNodeValue() { - return this.get().getValue(); + HtmlToken ht = this.get(); + if (null != ht) { + return ht.getValue(); + } + return null; } public String getText() { @@ -215,6 +258,17 @@ else if (tag.isNoChild()) { return sb.toString(); } + public String getTextContent() { + String re = this.getText(); + if (Strings.isBlank(re)) { + re = this.getNodeValue(); + } + if (Strings.isBlank(re)) { + re = this.htmlSegment; + } + return re; + } + public Tag setText(String text) { this.add(Tag.text(text)); return this; @@ -229,10 +283,12 @@ public List childrenTag() { return list; } + @Override public String toString() { return toString(0); } + @Override public String toString(int level) { StringBuilder sb = new StringBuilder(); __join_to_string(sb, this, level, true, null); @@ -263,8 +319,9 @@ public String toInnerHtml(boolean autoIndent, Callback tagWatcher) { __join_to_string(sb, childTag, level, false, tagWatcher); - if (childTag.isBlock() || childTag.isBody()) + if (childTag.isBlock() || childTag.isBody()) { sb.append('\n'); + } } return sb.toString(); } @@ -279,6 +336,12 @@ private static void __join_to_string(StringBuilder sb, tagWatcher.invoke(tag); } + // HTML 片段 + if (null != tag.htmlSegment) { + sb.append(tag.htmlSegment); + return; + } + // 纯文本 if (tag.get().isText()) { sb.append(tag.get().getValue()); @@ -293,8 +356,9 @@ private static void __join_to_string(StringBuilder sb, __join_tag_prefix(sb, tag, prefix); sb.append('<').append(tag.name()); __join_attributes(sb, tag); - if (closeNoChild) + if (closeNoChild) { sb.append('/'); + } sb.append('>'); } // 行内元素 @@ -314,8 +378,9 @@ else if (tag.isInline()) { for (Node child : tag.getChildren()) { Tag childTag = (Tag) child; - if (childTag.isBlock() || childTag.isBody()) + if (childTag.isBlock() || childTag.isBody()) { sb.append('\n'); + } __join_to_string(sb, childTag, @@ -330,8 +395,9 @@ else if (tag.isInline()) { } private static void __join_tag_prefix(StringBuilder sb, Tag tag, String prefix) { - if (null != prefix && prefix.length() > 0) + if (null != prefix && prefix.length() > 0) { sb.append(prefix); + } } private static void __join_tag_begin(StringBuilder sb, Tag tag) { @@ -345,28 +411,49 @@ private static void __join_tag_end(StringBuilder sb, Tag tag) { } private static void __join_attributes(StringBuilder sb, Tag tag) { - for (Pair attr : tag.get().getAttributes()) - sb.append(' ').append(attr.toString()); + for (Pair attr : tag.get().getAttributes()) { + String name = attr.getName(); + String n2 = name.toLowerCase(); + // 无需 value 节点 + if (n2.matches("^(disabled|checked)$")) { + sb.append(' ').append(name); + } + // 输出值 + else { + sb.append(' ').append(attr.toString()); + } + } } + @Override @SuppressWarnings("rawtypes") public void toXml(StringBuilder sb, int level) { - if (level == 0) + if (level == 0) { sb.append(Xmls.HEAD); - if (sb.length() > 2 && sb.charAt(sb.length() - 1) != '\n') + } + if (sb.length() > 2 && sb.charAt(sb.length() - 1) != '\n') { sb.append("\r\n"); - if (level > 0) - sb.append(Strings.dup(' ', level*2)); - __join_tag_begin(sb, this); - if (getChildren().size() == 1) { - sb.append(getText()); } - else if (hasChild()) { - for (Node node : getChildren()) { - node.toXml(sb, level+1); + if (level > 0) { + sb.append(Strings.dup(' ', level * 2)); + } + __join_tag_begin(sb, this); + if (hasChild()) { + boolean flag = true; + if (getChildren().size() == 1) { + if (getChildren().get(0).get().getName() == null) { + sb.append(getText()); + flag = false; + } + } + if (flag) { + for (Node node : getChildren()) { + node.toXml(sb, level + 1); + } + if (level > 0) { + sb.append(Strings.dup(' ', level * 2)); + } } - if (level > 0) - sb.append(Strings.dup(' ', level*2)); } __join_tag_end(sb, this); sb.append("\r\n"); diff --git a/src/org/nutz/log/impl/Slf4jLogAdapter.java b/src/org/nutz/log/impl/Slf4jLogAdapter.java new file mode 100644 index 0000000000..af5db054c1 --- /dev/null +++ b/src/org/nutz/log/impl/Slf4jLogAdapter.java @@ -0,0 +1,87 @@ +package org.nutz.log.impl; + +import org.nutz.log.Log; +import org.nutz.log.LogAdapter; +import org.nutz.plugin.Plugin; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.spi.LocationAwareLogger; + +/** + * 让Nutz的日志走Slf4j的API + */ +public class Slf4jLogAdapter implements LogAdapter, Plugin { + + public Log getLogger(String className) { + Logger logger = LoggerFactory.getLogger(className); + if (logger instanceof LocationAwareLogger) + return new Slf4jLogger((LocationAwareLogger) logger); + // 总有人用slf4j-simple的? 成全他们吧 + return new SimpleSlf4jLogger(logger); + } + + static class SimpleSlf4jLogger extends AbstractLog { + protected Logger logger; + + public SimpleSlf4jLogger(Logger logger) { + this.logger = logger; + } + + public void warn(Object message, Throwable t) { + logger.warn(String.valueOf(message), t); + } + + public void trace(Object message, Throwable t) { + logger.trace(String.valueOf(message), t); + } + + public void info(Object message, Throwable t) { + logger.info(String.valueOf(message), t); + } + + public void fatal(Object message, Throwable t) { + logger.error(String.valueOf(message), t); + } + + public void error(Object message, Throwable t) { + logger.error(String.valueOf(message), t); + } + + public void debug(Object message, Throwable t) { + logger.debug(String.valueOf(message), t); + } + + protected void log(int level, Object message, Throwable tx) { + String msg = String.valueOf(message); + switch (level) { + case AbstractLog.LEVEL_FATAL: + case AbstractLog.LEVEL_ERROR: + logger.error(msg, tx); + break; + case AbstractLog.LEVEL_WARN: + logger.warn(msg, tx); + break; + case AbstractLog.LEVEL_INFO: + logger.info(msg, tx); + break; + case AbstractLog.LEVEL_TRACE: + logger.trace(msg, tx); + break; + case AbstractLog.LEVEL_DEBUG: + default: + logger.debug(msg, tx); + break; + } + } + } + + public boolean canWork() { + try { + Logger.class.getName(); + return true; + } + catch (Throwable e) {} + return false; + } + +} diff --git a/src/org/nutz/log/impl/Slf4jLogger.java b/src/org/nutz/log/impl/Slf4jLogger.java new file mode 100644 index 0000000000..603106f670 --- /dev/null +++ b/src/org/nutz/log/impl/Slf4jLogger.java @@ -0,0 +1,78 @@ +package org.nutz.log.impl; + +import org.slf4j.spi.LocationAwareLogger; + +public class Slf4jLogger extends AbstractLog { + + LocationAwareLogger logger; + + public static final String SUPER_FQCN = AbstractLog.class.getName(); + public static final String SELF_FQCN = Slf4jLogger.class.getName(); + + static final Object[] EMTRY = new Object[0]; + + public Slf4jLogger(LocationAwareLogger logger) { + this.logger = logger; + } + + public void fatal(Object message, Throwable t) { + if (this.isErrorEnabled()) + logger.log(null, SELF_FQCN, LocationAwareLogger.ERROR_INT, (String) message, EMTRY , t); + } + + public void error(Object message, Throwable t) { + if (this.isErrorEnabled()) + logger.log(null, SELF_FQCN, LocationAwareLogger.ERROR_INT, (String) message, EMTRY , t); + } + + public void warn(Object message, Throwable t) { + if (this.isWarnEnabled()) + logger.log(null, SELF_FQCN, LocationAwareLogger.WARN_INT, (String) message, EMTRY , t); + } + + public void info(Object message, Throwable t) { + if (this.isInfoEnabled()) + logger.log(null, SELF_FQCN, LocationAwareLogger.INFO_INT, (String) message, EMTRY , t); + } + + public void debug(Object message, Throwable t) { + if (this.isDebugEnabled()) + logger.log(null, SELF_FQCN, LocationAwareLogger.DEBUG_INT, (String) message, EMTRY , t); + } + + public void trace(Object message, Throwable t) { + if (this.isTraceEnabled()) + logger.log(null, SELF_FQCN, LocationAwareLogger.TRACE_INT, (String) message, EMTRY , t); + } + + protected void log(int level, Object message, Throwable tx) { + if (level == 50) + level = 40;// slf4j没有FATEL level + logger.log(null, SUPER_FQCN, level, (String) message, EMTRY , tx); + } + + + public boolean isDebugEnabled() { + return logger.isDebugEnabled(); + } + + public boolean isErrorEnabled() { + return logger.isErrorEnabled(); + } + + public boolean isFatalEnabled() { + return logger.isDebugEnabled(); + } + + public boolean isInfoEnabled() { + return logger.isInfoEnabled(); + } + + public boolean isTraceEnabled() { + return logger.isTraceEnabled(); + } + + public boolean isWarnEnabled() { + return logger.isWarnEnabled(); + } +} diff --git a/src/org/nutz/log/impl/SystemLogAdapter.java b/src/org/nutz/log/impl/SystemLogAdapter.java index 1c38594cdf..9b8d1044db 100644 --- a/src/org/nutz/log/impl/SystemLogAdapter.java +++ b/src/org/nutz/log/impl/SystemLogAdapter.java @@ -9,10 +9,12 @@ public class SystemLogAdapter implements LogAdapter, Plugin { + @Override public Log getLogger(String className) { return SystemLog.me(); } + @Override public boolean canWork() { return true; } @@ -42,69 +44,84 @@ private SystemLog() { isDebugEnabled = true; } + @Override public void debug(Object message, Throwable t) { - if (isDebugEnabled()) + if (isDebugEnabled()) { printOut("DEBUG",message, t); + } } + @Override public void error(Object message, Throwable t) { - if (isErrorEnabled()) + if (isErrorEnabled()) { errorOut("ERROR",message, t); + } } + @Override public void fatal(Object message, Throwable t) { - if (isFatalEnabled()) + if (isFatalEnabled()) { errorOut("FATAL",message, t); + } } + @Override public void info(Object message, Throwable t) { - if (isInfoEnabled()) + if (isInfoEnabled()) { printOut("INFO",message, t); + } } + @Override public void trace(Object message, Throwable t) { - if (isTraceEnabled()) + if (isTraceEnabled()) { printOut("TRACE",message, t); + } } + @Override public void warn(Object message, Throwable t) { - if (isWarnEnabled()) + if (isWarnEnabled()) { errorOut("WARN",message, t); + } } private void printOut(String level, Object message, Throwable t) { System.out.printf("%s %s [%s] %s\n", Times.sDTms2(new Date()), level, Thread.currentThread().getName(),message); - if (t != null) + if (t != null) { t.printStackTrace(System.out); + } } private void errorOut(String level, Object message, Throwable t) { System.err.printf("%s %s [%s] %s\n", Times.sDTms2(new Date()), level, Thread.currentThread().getName(),message); - if (t != null) + if (t != null) { t.printStackTrace(System.err); + } } @Override protected void log(int level, Object message, Throwable tx) { switch (level) { - case LEVEL_FATAL: - fatal(message, tx); - break; - case LEVEL_ERROR: - error(message, tx); - break; - case LEVEL_WARN: - warn(message, tx); - break; - case LEVEL_INFO: - info(message, tx); - break; - case LEVEL_DEBUG: - debug(message, tx); - break; - case LEVEL_TRACE: - trace(message, tx); - break; + case LEVEL_FATAL: + fatal(message, tx); + break; + case LEVEL_ERROR: + error(message, tx); + break; + case LEVEL_WARN: + warn(message, tx); + break; + case LEVEL_INFO: + info(message, tx); + break; + case LEVEL_DEBUG: + debug(message, tx); + break; + case LEVEL_TRACE: + trace(message, tx); + break; + default: } } diff --git a/src/org/nutz/mapl/impl/MaplRebuild.java b/src/org/nutz/mapl/impl/MaplRebuild.java index 3c1f48de05..7db65466a7 100644 --- a/src/org/nutz/mapl/impl/MaplRebuild.java +++ b/src/org/nutz/mapl/impl/MaplRebuild.java @@ -8,6 +8,7 @@ import java.util.Map; import org.nutz.lang.Lang; +import org.nutz.lang.Nums; import org.nutz.lang.Strings; /** @@ -136,35 +137,40 @@ private Object inject(Object obj, int i) { if (!map.containsKey(k)) { map.put(k, new ArrayList()); } - int index = fetchIndex(key.substring(key.indexOf('['), key.length())); + int[] index = fetchIndex(key.substring(key.indexOf('['), key.length())); injectList((List) map.get(k), i, index); return map; } + // 键值:这里有个特殊考虑,如果当前对象是个列表,那么键值必然是一个下标 if (obj instanceof List) { try { int index = Integer.parseInt(keys[i]); - injectList((List) obj, i, index); + injectList((List) obj, i, Nums.array(index)); return obj; } catch (Exception e) { throw new RuntimeException("路径格式不正确!"); } } + // 当做普通的 map 好了 return injectMap(obj, i); } - private int fetchIndex(String val) { - int index = 0; + private int[] fetchIndex(String val) { + // []格式的路径, 即索引放在arrayIndex里面的. if (val.indexOf(']') == 1) { - // []格式的路径, 即索引放在arrayIndex里面的. if (arrayIndex.size() > arrayItem) { - index = arrayIndex.get(arrayItem++); + return Nums.array(arrayIndex.get(arrayItem++)); } - } else { - // [1]格式, 路径上自带索引 - index = Integer.parseInt(val.substring(1, val.length() - 1)); + // 默认返回 0 + return Nums.array(0); } - return index; + // [1]格式, 路径上自带索引,可以是多个,譬如[1][3][0] + String[] ss = val.substring(1, val.length() - 1).split("\\]\\["); + int[] re = new int[ss.length]; + for (int i = 0; i < ss.length; i++) + re[i] = Integer.parseInt(ss[i]); + return re; } /** @@ -220,45 +226,70 @@ private Object injectMap(Object obj, int i) { * 注入List * * @param list - * @param i + * 列表 + * @param keyIndex + * 当前 Key 路径的列表 + * @param eleIndexes + * 注入的元素下标列表 */ @SuppressWarnings("unchecked") - private void injectList(List list, int i, int index) { + private void injectList(List list, int keyIndex, int[] eleIndexes) { + // 下标列表如果是多个,那么预先处理一下列表 + int i_last = eleIndexes.length - 1; + for (int i = 0; i < i_last; i++) { + int index = eleIndexes[i]; + Object ele = list.get(index); + // 是列表?嗯很好很好 + if (ele instanceof List) { + list = (List) ele; + } + // 不是列表啊,不能忍 + else { + throw Lang.makeThrow("invalid keyPath '%s' in key:%d eleIndex:%d", + Strings.join(".", keys), + keyIndex, + i); + } + } + + // 得到要处理的下标 + int eleIndex = eleIndexes[i_last]; + // 添加模式 if (model == Model.add) { - if (i == keys.length - 1) { + if (keyIndex == keys.length - 1) { if (val instanceof Collection) { list.addAll((Collection) val); } else { - list.add(index, val); + list.add(eleIndex, val); } return; } - if (list.size() <= index) { - list.add(index, new LinkedHashMap()); + if (list.size() <= eleIndex) { + list.add(eleIndex, new LinkedHashMap()); } } else if (model == Model.del) { - if (i == keys.length - 1) { - if (list.size() > index) { - list.remove(index); + if (keyIndex == keys.length - 1) { + if (list.size() > eleIndex) { + list.remove(eleIndex); } return; } - if (list.size() <= index) { + if (list.size() <= eleIndex) { return; } } else if (model == Model.cell) { - if (i == keys.length - 1) { - if (list.size() > index) { - cellObj = list.get(index); + if (keyIndex == keys.length - 1) { + if (list.size() > eleIndex) { + cellObj = list.get(eleIndex); } return; } - if (list.size() <= index) { + if (list.size() <= eleIndex) { return; } } - inject((Map) list.get(index), i + 1); + inject((Map) list.get(eleIndex), keyIndex + 1); } } \ No newline at end of file diff --git a/src/org/nutz/mapl/impl/compile/ObjCompileImpl.java b/src/org/nutz/mapl/impl/compile/ObjCompileImpl.java index 4e0778ca9c..4f64936d8e 100644 --- a/src/org/nutz/mapl/impl/compile/ObjCompileImpl.java +++ b/src/org/nutz/mapl/impl/compile/ObjCompileImpl.java @@ -118,18 +118,8 @@ private Map pojo2Json(Object obj, Map map) { List fields = jen.getFields(); ArrayList list = new ArrayList(fields.size()); for (JsonEntityField jef : fields) { - String name = jef.getName(); try { - Object value = jef.getValue(obj); - if (null != value) { - // 递归 - Mirror mirror = Mirror.me(value); - if (mirror.isPojo()) { - value = parse(value); - } - } - // 加入输出列表 ... - list.add(new Pair(name, value)); + list.add(new Pair(jef.getName(), parse(jef.getValue(obj)))); } catch (FailToGetValueException e) {} } diff --git a/src/org/nutz/mapl/impl/convert/ObjConvertImpl.java b/src/org/nutz/mapl/impl/convert/ObjConvertImpl.java index 78b194b094..94af9d2b94 100644 --- a/src/org/nutz/mapl/impl/convert/ObjConvertImpl.java +++ b/src/org/nutz/mapl/impl/convert/ObjConvertImpl.java @@ -2,6 +2,7 @@ import java.lang.reflect.Array; import java.lang.reflect.Type; +import java.text.DateFormat; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -34,11 +35,11 @@ public class ObjConvertImpl implements MaplConvert { // 路径 - Stack path = new Stack(); + protected Stack path = new Stack(); // 对象缓存 - Context context; + protected Context context; - private Type type; + protected Type type; public ObjConvertImpl(Type type) { this.type = type; @@ -64,14 +65,17 @@ public Object convert(Object model) { if (type == null) return model; // obj是基本数据类型或String - if (!(model instanceof Map) && !(model instanceof List)) { + Mirror mirror = Mirror.me(type); + if (mirror.isSimple()) + return Castors.me().castTo(model, mirror.getType()); + if (!(model instanceof Map) && !(model instanceof Iterable)) { return Castors.me().castTo(model, Lang.getTypeClass(type)); } return inject(model, type); } - Object inject(Object model, Type type) { + public Object inject(Object model, Type type) { if (model == null) { return null; } @@ -236,6 +240,14 @@ private Object injectObj(Object model, Mirror mirror) { path.push(key); } } + // fix issue 1386 + if (jef.getMirror().isDateTimeLike() && jef.getDataFormat() != null) { + try { + jef.setValue(obj, ((DateFormat)jef.getDataFormat()).parse(String.valueOf(val))); + continue; + } catch (Throwable e) { + } + } jef.setValue(obj, Mapl.maplistToObj(val, jef.getGenericType())); } return obj; diff --git a/src/org/nutz/mapl/impl/convert/StructureConvert.java b/src/org/nutz/mapl/impl/convert/StructureConvert.java index 47f4d27539..4d4d7843af 100644 --- a/src/org/nutz/mapl/impl/convert/StructureConvert.java +++ b/src/org/nutz/mapl/impl/convert/StructureConvert.java @@ -94,21 +94,24 @@ public StructureConvert(Object obj){ * 转换 * @param obj 目标对象 */ + @Override public Object convert(Object obj){ each(obj); return structure.fetchNewobj(); } + @Override protected void LRD(String path, Object item) {} /** * 重建新对象 */ + @Override protected void DLR(String path, Object object) { if(relation.containsKey(path)){ List dests = relation.get(path); for(String dest : dests){ - if(dest.equals("")){ + if("".equals(dest)){ structure.put(path, object, arrayIndex); continue; } diff --git a/src/org/nutz/mvc/ActionContext.java b/src/org/nutz/mvc/ActionContext.java index 3c577b6e11..43bdc065c2 100644 --- a/src/org/nutz/mvc/ActionContext.java +++ b/src/org/nutz/mvc/ActionContext.java @@ -1,13 +1,19 @@ package org.nutz.mvc; import java.lang.reflect.Method; +import java.util.HashMap; import java.util.List; +import java.util.Map; import javax.servlet.ServletContext; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.nutz.ioc.Ioc; +import org.nutz.json.Json; +import org.nutz.json.JsonFormat; import org.nutz.lang.util.SimpleContext; /** @@ -35,6 +41,8 @@ public class ActionContext extends SimpleContext { private static final String ERROR = "nutz.mvc.error"; public static final String AC_DONE = "nutz.mvc.done"; + + public static final String REFER_OBJECT = "nutz.mvc.refer_object"; /** * 获取全局的Ioc对象 @@ -112,6 +120,11 @@ public ActionContext setPathArgs(List args) { return this; } + public ActionContext setNamedPathArgs(Map args) { + this.set(PATH_ARGS, args); + return this; + } + /** * 获取这个Action对应的Method */ @@ -212,8 +225,34 @@ public ActionContext setServletContext(ServletContext sc) { this.set(SERVLET_CONTEXT, sc); return this; } + + public ActionContext setReferObject(Object value) { + this.set(REFER_OBJECT, value); + return this; + } + + public Object getReferObject() { + return get(REFER_OBJECT); + } + + protected Map getSafeMap() { + Map map = new HashMap(); + for (Map.Entry en : getInnerMap().entrySet()) { + Object value = en.getValue(); + if (value != null) { + if (value instanceof ServletRequest || value instanceof ServletResponse || value instanceof ServletContext || value instanceof Ioc) + continue; + map.put(en.getKey(), value); + } + } + return map; + } + + public String toJson(JsonFormat jf) { + return Json.toJson(getSafeMap(), jf); + } public String toString() { - return getInnerMap().toString(); + return getSafeMap().toString(); } } diff --git a/src/org/nutz/mvc/ActionInfo.java b/src/org/nutz/mvc/ActionInfo.java index d8f9246503..15efc24a7c 100644 --- a/src/org/nutz/mvc/ActionInfo.java +++ b/src/org/nutz/mvc/ActionInfo.java @@ -6,13 +6,13 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import org.nutz.lang.Lang; +import org.nutz.lang.Strings; import org.nutz.lang.util.ClassMeta; import org.nutz.lang.util.ClassMetaReader; -import java.util.Set; - public class ActionInfo { private String inputEncoding; @@ -54,12 +54,14 @@ public class ActionInfo { private Integer lineNumber; private Object obj;// + + private String[] namedPathArgs; public ActionInfo() { httpMethods = new HashSet(); } - public ActionInfo mergeWith(ActionInfo parent) { + public ActionInfo mergeWith(ActionInfo parent, boolean fromMain) { // 组合路径 - 与父路径做一个笛卡尔积 if (!pathTop && null != paths && null != parent.paths && parent.paths.length > 0) { List myPaths = new ArrayList(paths.length * parent.paths.length); @@ -94,8 +96,10 @@ else if (paths == null && parent.paths != null && parent.paths.length > 0) { okView = null == okView ? parent.okView : okView; failView = null == failView ? parent.failView : failView; filterInfos = null == filterInfos ? parent.filterInfos : filterInfos; - injectName = null == injectName ? parent.injectName : injectName; - moduleType = null == moduleType ? parent.moduleType : moduleType; + if (!fromMain) { + injectName = null == injectName ? parent.injectName : injectName; + moduleType = null == moduleType ? parent.moduleType : moduleType; + } chainName = null == chainName ? parent.chainName : chainName; // 继承元数据信息 @@ -107,6 +111,28 @@ else if (paths == null && parent.paths != null && parent.paths.length > 0) { } } + // 当前仅支持单一路径的时候使用路径占位符 + if (this.method != null && paths != null && paths.length == 1) { + String path = paths[0]; + if (path.contains("{")) { + String[] tmp = Strings.splitIgnoreBlank(path, "/"); + List ph = new ArrayList(); + for (int j = 0; j < tmp.length; j++) { + String p = tmp[j]; + if (p.length() > 2 && p.startsWith("{") && p.endsWith("}")) { + String named = p.substring(1, p.length() - 1).trim(); + tmp[j] = "?"; + ph.add(named); + } + else if ("?".equals(p)) { + ph.add("arg" + ph.size()); + } + } + paths[0] = "/" + Strings.join("/", tmp); + namedPathArgs = ph.toArray(new String[ph.size()]); + } + } + return this; } @@ -269,4 +295,8 @@ public void setModuleObj(Object obj) { public Object getModuleObj() { return this.obj; } + + public String[] getNamedPathArgs() { + return namedPathArgs; + } } diff --git a/src/org/nutz/mvc/CommandLineRunner.java b/src/org/nutz/mvc/CommandLineRunner.java new file mode 100644 index 0000000000..6337c0208a --- /dev/null +++ b/src/org/nutz/mvc/CommandLineRunner.java @@ -0,0 +1,15 @@ +package org.nutz.mvc; + +/** + * @author 黄川 huchuc@vip.qq.com + */ +@FunctionalInterface +public interface CommandLineRunner { + + /** + * Callback used to run the bean. + * @throws Exception on error + */ + void run() throws Exception; + +} diff --git a/src/org/nutz/mvc/Loading.java b/src/org/nutz/mvc/Loading.java index b18151a0d3..2c02f11c21 100644 --- a/src/org/nutz/mvc/Loading.java +++ b/src/org/nutz/mvc/Loading.java @@ -3,7 +3,7 @@ public interface Loading { - public static final String CONTEXT_NAME = "_NUTZ_LOADING_CONTEXT_"; + String CONTEXT_NAME = "_NUTZ_LOADING_CONTEXT_"; UrlMapping load(NutConfig config); diff --git a/src/org/nutz/mvc/MvcI18n.java b/src/org/nutz/mvc/MvcI18n.java new file mode 100644 index 0000000000..9eefa68561 --- /dev/null +++ b/src/org/nutz/mvc/MvcI18n.java @@ -0,0 +1,62 @@ +package org.nutz.mvc; + +import java.text.MessageFormat; +import java.util.Map; + +/** + * web环境下 国际化 相关帮助函数 + * + * @author 306955302@qq.com + */ +public class MvcI18n { + + /** + * 取得国际化信息 + * + * @param key + * @return + */ + public static String message(String key) { + return messageOrDefault(key, ""); + } + + /** + * 取得国际化信息并格式化 + * {0}帐号登录{1} ->> test帐号登录失败 + * + * @param key + * @return + */ + public static String message(String key, Object... params) { + return MessageFormat.format(messageOrDefault(key, ""), params); + } + + /** + * 取得国际化信息并格式化 + * {0}帐号登录{1} ->> test帐号登录失败 + * + * @param key + * @return + */ + public static String messageOrDefaultFormat(String key, String defaultValue, Object... params) { + return MessageFormat.format(messageOrDefault(key, defaultValue), params); + } + + /** + * 取得国际化信息 + * + * @param key + * @return + */ + public static String messageOrDefault(String key, String defaultValue) { + String localizationKey = Mvcs.getLocalizationKey() == null ? Mvcs.getDefaultLocalizationKey() : Mvcs.getLocalizationKey(); + Map localization = Mvcs.getLocaleMessage(localizationKey); + if (null != localization) { + Object value = localization.get(key); + return value == null ? defaultValue : String.valueOf(value); + } + return defaultValue; + } + +} + diff --git a/src/org/nutz/mvc/Mvcs.java b/src/org/nutz/mvc/Mvcs.java index 630d9866be..d392286e43 100644 --- a/src/org/nutz/mvc/Mvcs.java +++ b/src/org/nutz/mvc/Mvcs.java @@ -9,6 +9,7 @@ import java.util.Map; import java.util.Set; +import javax.servlet.RequestDispatcher; import javax.servlet.ServletContext; import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; @@ -16,6 +17,7 @@ import javax.servlet.http.HttpSession; import org.nutz.Nutz; +import org.nutz.conf.NutConf; import org.nutz.ioc.Ioc; import org.nutz.ioc.IocContext; import org.nutz.json.Json; @@ -24,12 +26,13 @@ import org.nutz.lang.util.Context; import org.nutz.lang.util.NutMap; import org.nutz.mvc.config.AtMap; +import org.nutz.mvc.i18n.LocalizationManager; import org.nutz.mvc.impl.NutMessageMap; import org.nutz.mvc.ioc.SessionIocContext; /** * Mvc 相关帮助函数 - * + * * @author zozoh(zozohtnt@gmail.com) * @author wendal(wendal1985@gmail.com) */ @@ -47,23 +50,34 @@ public abstract class Mvcs { public static boolean DISPLAY_METHOD_LINENUMBER = true; // 如果一个Resp已经commit过了,那么是否跳过渲染呢 public static boolean SKIP_COMMITTED = false; - + public static boolean DISABLE_X_POWERED_BY = false; - + public static String X_POWERED_BY = "nutz/"+Nutz.version()+" "; + + public static LocalizationManager localizationManager; + + public static void setLocalizationManager(LocalizationManager localizationManager) { + Mvcs.localizationManager = localizationManager; + } // ==================================================================== - public static Map getLocaleMessage(String key) { - Map> msgss = getMessageSet(); - if (null != msgss) - return msgss.get(key); - return null; + public static Map getLocaleMessage(String local) { + if (localizationManager != null) { + return localizationManager.getMessageMap(local); + } + else { + Map> msgss = getMessageSet(); + if (null != msgss) + return msgss.get(local); + return null; + } } /** * 获取当前请求对象的字符串表 - * + * * @param req * 请求对象 * @return 字符串表 @@ -75,7 +89,7 @@ public static Map getMessages(ServletRequest req) { /** * 获取当前请求对象的字符串表(NutMessageMap 封装) - * + * * @param req * 请求对象 * @return 字符串表 @@ -86,7 +100,7 @@ public static NutMessageMap getMessageMap(ServletRequest req) { /** * 获取当前请求对象的字符串表中的某一个字符串 - * + * * @param req * 请求对象 * @param key @@ -102,7 +116,7 @@ public static String getMessage(ServletRequest req, String key) { /** * 获取当前会话的本地字符串集合的键值;如果当前 HTTP 会话不存在,则返回 null - * + * * @return 当前会话的本地字符串集合的键值;如果当前 HTTP 会话不存在,则返回 null */ public static String getLocalizationKey() { @@ -114,7 +128,7 @@ public static String getLocalizationKey() { *

* 如果你用的是 Nutz.Mvc 默认的本地化机制,那么你的本地字符串键值,相当于一个你目录名。
* 比如 "zh_CN" 等 - * + * * @param key * 键值 * @return 是否设置成功 @@ -129,7 +143,7 @@ public static boolean setLocalizationKey(String key) { /** * 返回当前加载了的本地化字符串的键值 - * + * * @return 当前都加载了哪些种字符串的 KEY */ public static Set getLocalizationKeySet() { @@ -146,7 +160,7 @@ public static Set getLocalizationKeySet() { /** * 设置默认的多国语言 - * + * * @param key * 默认的多国语言 KEY */ @@ -156,7 +170,7 @@ public static void setDefaultLocalizationKey(String key) { /** * 返回默认的本地化字符串 KEY - * + * * @return 默认的本地化字符串 KEY */ public static String getDefaultLocalizationKey() { @@ -169,40 +183,54 @@ public static String getDefaultLocalizationKey() { *

  • 本地化子字符串 => ${msg} *
  • 应用的路径名 => ${base} * - * + * * @param req * HTTP 请求对象 */ public static void updateRequestAttributes(HttpServletRequest req) { // 初始化本次请求的多国语言字符串 - Map> msgss = getMessageSet(); - if (msgss == null && !ctx().localizations.isEmpty()) - msgss = ctx().localizations.values().iterator().next(); - if (null != msgss) { - Map msgs = null; - - String lKey = Strings.sBlank(Mvcs.getLocalizationKey(), getDefaultLocalizationKey()); - - if (!Strings.isBlank(lKey)) - msgs = msgss.get(lKey); - - // 没有设定特殊的 Local 名字,随便取一个 - if (null == msgs) { - if (msgss.size() > 0) - msgs = msgss.values().iterator().next(); + if (localizationManager == null) { + Map> msgss = getMessageSet(); + if (msgss == null && !ctx().localizations.isEmpty()) + msgss = ctx().localizations.values().iterator().next(); + if (null != msgss) { + Map msgs = null; + + String lKey = Strings.sBlank(Mvcs.getLocalizationKey(), getDefaultLocalizationKey()); + + if (!Strings.isBlank(lKey)) + msgs = msgss.get(lKey); + + // 没有设定特殊的 Local 名字,随便取一个 + if (null == msgs) { + if (msgss.size() > 0) + msgs = msgss.values().iterator().next(); + } + // 记录到请求中 + req.setAttribute(MSG, msgs); } - // 记录到请求中 - req.setAttribute(MSG, msgs); + } + else { + String lKey = Mvcs.getLocalizationKey(); + if (Strings.isBlank(lKey)) { + lKey = localizationManager.getDefaultLocal(); + if (Strings.isBlank(lKey)) + lKey = getDefaultLocalizationKey(); + } + NutMessageMap msg = localizationManager.getMessageMap(lKey); + if (msg != null) + req.setAttribute(MSG, msg); } // 记录一些数据到请求对象中 req.setAttribute("base", req.getContextPath()); - req.setAttribute("$request", req); + if (NutConf.MVC_ADD_ATTR_$REQUEST) + req.setAttribute("$request", req); } /** * 获取当前请求的路径,并去掉后缀 - * + * * @param req * HTTP 请求对象 */ @@ -212,11 +240,21 @@ public static String getRequestPath(HttpServletRequest req) { /** * 获取当前请求的路径,并去掉后缀 - * + * * @param req * HTTP 请求对象 */ public static RequestPath getRequestPathObject(HttpServletRequest req) { + Object includeUrl = req.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO); + if (includeUrl != null) { + return getRequestPathObject(includeUrl.toString()); + } + + includeUrl = req.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH); + if (includeUrl != null) { + return getRequestPathObject(includeUrl.toString()); + } + String url = req.getPathInfo(); if (null == url) url = req.getServletPath(); @@ -225,7 +263,7 @@ public static RequestPath getRequestPathObject(HttpServletRequest req) { /** * 获取当前请求的路径,并去掉后缀 - * + * * @param url * 请求路径的URL */ @@ -256,7 +294,7 @@ public static RequestPath getRequestPathObject(String url) { /** * 注销当前 HTTP 会话。所有 Ioc 容器存入的对象都会被注销 - * + * * @param session * HTTP 会话对象 */ @@ -267,7 +305,7 @@ public static void deposeSession(HttpSession session) { /** * 它将对象序列化成 JSON 字符串,并写入 HTTP 响应 - * + * * @param resp * 响应对象 * @param obj @@ -281,7 +319,7 @@ public static void write(HttpServletResponse resp, Object obj, JsonFormat format throws IOException { write(resp, resp.getWriter(), obj, format); } - + public static void write(HttpServletResponse resp, Writer writer, Object obj, JsonFormat format) throws IOException { resp.setHeader("Cache-Control", "no-cache"); @@ -324,7 +362,7 @@ public static NutMvcContext ctx() { /** * 获取 HTTP 请求对象 - * + * * @return HTTP 请求对象 */ public static final HttpServletRequest getReq() { @@ -333,7 +371,7 @@ public static final HttpServletRequest getReq() { /** * 获取 HTTP 响应对象 - * + * * @return HTTP 响应对象 */ public static final HttpServletResponse getResp() { @@ -346,7 +384,7 @@ public static final String getName() { /** * 获取 Action 执行的上下文 - * + * * @return Action 执行的上下文 */ public static final ActionContext getActionContext() { @@ -361,7 +399,7 @@ public static void set(String name, HttpServletRequest req, HttpServletResponse /** * 设置 Servlet 执行的上下文 - * + * * @param servletContext * Servlet 执行的上下文 */ @@ -376,7 +414,7 @@ public static void setServletContext(ServletContext servletContext) { /** * 设置 Action 执行的上下文 - * + * * @param actionContext * Action 执行的上下文 */ @@ -386,7 +424,7 @@ public static void setActionContext(ActionContext actionContext) { /** * 获取 Servlet 执行的上下文 - * + * * @return Servlet 执行的上下文 */ public static ServletContext getServletContext() { @@ -398,7 +436,7 @@ public static ServletContext getServletContext() { /** * 设置对象装配的上下文环境 - * + * * @param iocContext * 对象装配的上下文环境 */ @@ -408,7 +446,7 @@ public static void setIocContext(IocContext iocContext) { /** * 获取对象装配的上下文环境 - * + * * @return 进行对象装配的上下文环境 */ public static IocContext getIocContext() { @@ -420,7 +458,7 @@ public static IocContext getIocContext() { /** * 获取全局的Ioc对象 - * + * * @return 全局的Ioc对象 */ public static Ioc getIoc() { @@ -483,21 +521,21 @@ public static void close() { ctx().close(); ctx = new NutMvcContext(); } - + public static Context reqt() { return ctx().reqCtx(); } - + public static Object getSessionAttrSafe(String key) { try { HttpSession session = getHttpSession(false); return session != null ? session.getAttribute(key) : null; } - catch (Exception e) { - return false; + catch (Throwable e) { + return null; } } - + public static void setSessionAttrSafe(String key, Object val, boolean sessionCreate) { try { HttpSession session = getHttpSession(sessionCreate); @@ -507,7 +545,7 @@ public static void setSessionAttrSafe(String key, Object val, boolean sessionCre catch (Exception e) { } } - + public static NutMap toParamMap(Reader r, String enc) throws IOException { try { NutMap map = new NutMap(); @@ -535,6 +573,6 @@ public static NutMap toParamMap(Reader r, String enc) throws IOException { throw new IOException(e); } } - - + + } diff --git a/src/org/nutz/mvc/RequestMatcher.java b/src/org/nutz/mvc/RequestMatcher.java new file mode 100644 index 0000000000..8313a15a40 --- /dev/null +++ b/src/org/nutz/mvc/RequestMatcher.java @@ -0,0 +1,8 @@ +package org.nutz.mvc; + +public interface RequestMatcher { + + void add(String path, ActionInfo ai, ActionChain chain); + + ActionChain match(ActionContext ctx); +} diff --git a/src/org/nutz/mvc/ViewContextCollector.java b/src/org/nutz/mvc/ViewContextCollector.java new file mode 100644 index 0000000000..515dfae8ff --- /dev/null +++ b/src/org/nutz/mvc/ViewContextCollector.java @@ -0,0 +1,19 @@ +package org.nutz.mvc; + +import javax.servlet.http.HttpServletRequest; + +import org.nutz.lang.util.Context; + +/** + * 视图上下文收集器 + * @param + */ +public interface ViewContextCollector { + /** + * 收集上下依赖的信息 + * @param req + * @param obj + * @return + */ + public Context collect(HttpServletRequest req, Object obj); +} diff --git a/src/org/nutz/mvc/WhaleFilter.java b/src/org/nutz/mvc/WhaleFilter.java index 46d69442ba..8af3883791 100644 --- a/src/org/nutz/mvc/WhaleFilter.java +++ b/src/org/nutz/mvc/WhaleFilter.java @@ -29,6 +29,7 @@ import org.nutz.lang.Each; import org.nutz.lang.Lang; import org.nutz.lang.Mirror; +import org.nutz.lang.Strings; import org.nutz.lang.util.NutMap; import org.nutz.log.LogAdapter; import org.nutz.log.Logs; @@ -57,6 +58,13 @@ public void init(FilterConfig c) throws ServletException { sc = c.getServletContext(); _me = this; try { + Enumeration keys = c.getInitParameterNames(); + while (keys.hasMoreElements()) { + String key = keys.nextElement(); + String value = c.getInitParameter(key); + if (!Strings.isBlank(value) && !"null".equals(value)) + props.put(key, c.getInitParameter(key)); + } String path = c.getInitParameter("config-file"); if (path != null) { InputStream ins = getClass().getClassLoader().getResourceAsStream(path); @@ -71,16 +79,20 @@ public void init(FilterConfig c) throws ServletException { if (config != null) { init(new ByteArrayInputStream(config.getBytes())); } + else { + init((InputStream)null); + } } } catch (Exception e) { throw new ServletException(e); } } - + public void init(InputStream ins) throws Exception { - props.load(ins); - if (props.contains("log.adapter")) { + if (ins != null) + props.load(ins); + if (props.containsKey("log.adapter")) { LogAdapter la = (LogAdapter) Class.forName(props.getProperty("log.adapter")).newInstance(); Logs.setAdapter(la); } @@ -100,7 +112,7 @@ public void init(InputStream ins) throws Exception { continue; } key = key.substring("upload.".length()); - if ("tmpdir".equals(key) || "exclusions".equals(key)) { + if ("tmpdir".equals(key) || "exclusions".equals(key) || "enable".equals(key)) { continue; } mirror.setValue(uc, key, props.get(_key)); @@ -170,11 +182,11 @@ public String getMethod() { catch (Exception e) { } } - + } public void destroy() {} - + @SuppressWarnings("unchecked") public HttpServletRequest handleUpload(HttpServletRequest req) throws ServletException { try { diff --git a/src/org/nutz/mvc/adaptor/AbstractAdaptor.java b/src/org/nutz/mvc/adaptor/AbstractAdaptor.java index a3da3bf989..1cd22956af 100644 --- a/src/org/nutz/mvc/adaptor/AbstractAdaptor.java +++ b/src/org/nutz/mvc/adaptor/AbstractAdaptor.java @@ -4,6 +4,7 @@ import java.io.Reader; import java.lang.annotation.Annotation; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.lang.reflect.Type; import javax.servlet.ServletContext; @@ -20,6 +21,7 @@ import org.nutz.lang.util.MethodParamNamesScaner; import org.nutz.log.Log; import org.nutz.log.Logs; +import org.nutz.mvc.ActionContext; import org.nutz.mvc.ActionInfo; import org.nutz.mvc.HttpAdaptor2; import org.nutz.mvc.Scope; @@ -42,6 +44,7 @@ import org.nutz.mvc.adaptor.injector.SessionAttrInjector; import org.nutz.mvc.adaptor.injector.SessionInjector; import org.nutz.mvc.adaptor.injector.ViewModelInjector; +import org.nutz.mvc.adaptor.injector.VoidInjector; import org.nutz.mvc.annotation.Attr; import org.nutz.mvc.annotation.Cookie; import org.nutz.mvc.annotation.IocObj; @@ -245,9 +248,12 @@ public Object[] adapt(ServletContext sc, errCtx = (AdaptorErrorContext) Mirror.me(argTypes[errCtxIndex]) .born(argTypes.length); - Object obj; + Object obj = req.getAttribute(ActionContext.REFER_OBJECT); try { - obj = getReferObject(sc, req, resp, pathArgs); + if (obj == null) { + obj = getReferObject(sc, req, resp, pathArgs); + req.setAttribute(ActionContext.REFER_OBJECT, obj); + } } catch (Throwable e) { if (errCtx != null) { @@ -345,6 +351,8 @@ protected ParamInjector paramNameInject(Method method, int index) { null, true); } + if (Modifier.isInterface(type.getModifiers())) + return new VoidInjector(); return new NameInjector(paramName, null, argTypes[index], diff --git a/src/org/nutz/mvc/adaptor/WhaleAdaptor.java b/src/org/nutz/mvc/adaptor/WhaleAdaptor.java index 987eca4c82..aa36f90ff1 100644 --- a/src/org/nutz/mvc/adaptor/WhaleAdaptor.java +++ b/src/org/nutz/mvc/adaptor/WhaleAdaptor.java @@ -10,6 +10,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.nutz.conf.NutConf; import org.nutz.filepool.FilePool; import org.nutz.filepool.UU32FilePool; import org.nutz.json.Json; @@ -18,6 +19,7 @@ import org.nutz.log.Log; import org.nutz.log.Logs; import org.nutz.mvc.Mvcs; +import org.nutz.mvc.adaptor.injector.MapPairInjector; import org.nutz.mvc.annotation.Param; import org.nutz.mvc.upload.FastUploading; import org.nutz.mvc.upload.FieldMeta; @@ -51,7 +53,7 @@ public WhaleAdaptor(String path) { } } if (path.isEmpty()) { - path = "${app.root}/WEB-INF/tmp/nutzupload2"; + path = (String) NutConf.getOrDefault("nutz.mvc.whale.defaultpath", "${app.root}/WEB-INF/tmp/nutzupload2"); } if (path.contains("${app.root}")) path = path.replace("${app.root}", appRoot); @@ -78,8 +80,16 @@ protected ParamInjector evalInjectorBy(Type type, Param param) { } // Map - if (Map.class.isAssignableFrom(clazz)) - return new MapSelfInjector(); + if (Map.class.isAssignableFrom(clazz)) { + final Class klass = clazz; + return new ParamInjector() { + public Object get(ServletContext sc, HttpServletRequest req, HttpServletResponse resp, Object refer) { + if (refer != null) + return refer; + return new MapPairInjector(klass).get(sc, req, resp, refer); + } + }; + } String pn = null == param ? getParamRealName(curIndex) : param.value(); diff --git a/src/org/nutz/mvc/adaptor/injector/ObjectNaviNode.java b/src/org/nutz/mvc/adaptor/injector/ObjectNaviNode.java index 1f2494b298..48a2e36e12 100644 --- a/src/org/nutz/mvc/adaptor/injector/ObjectNaviNode.java +++ b/src/org/nutz/mvc/adaptor/injector/ObjectNaviNode.java @@ -63,16 +63,17 @@ public void put(String path, String[] value) { for (; i < chars.length; i++) { char c2 = chars[i]; switch (c2) { - case ']': - case ')': - if ((c == '[' && c2 == ']') || (c == '(' && c2 == ')')) { - if (isNumber && !(c == '(')) { - sb.append(':').append(sb2); - } else { - sb.append('.').append(sb2); + case ']': + case ')': + if ((c == '[' && c2 == ']') || (c == '(' && c2 == ')')) { + if (isNumber && !(c == '(')) { + sb.append(':').append(sb2); + } else { + sb.append('.').append(sb2); + } + continue OUT; } - continue OUT; - } + default: } isNumber = isNumber && Character.isDigit(c2); sb2.append(c2); diff --git a/src/org/nutz/mvc/annotation/ApiVersion.java b/src/org/nutz/mvc/annotation/ApiVersion.java new file mode 100644 index 0000000000..f543d5f475 --- /dev/null +++ b/src/org/nutz/mvc/annotation/ApiVersion.java @@ -0,0 +1,30 @@ +package org.nutz.mvc.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; + +/** + * 定义@At里面{version}的版本号 + * @author Administrator + * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +@Documented +public @interface ApiVersion { + + /** + * 版本号,默认是v1 + * @return + */ + String value() default "v1"; + + /** + * 是否保留对应的路径参数,默认移除 + * @return + */ + boolean keepPathArg() default false; +} diff --git a/src/org/nutz/mvc/annotation/OPTIONS.java b/src/org/nutz/mvc/annotation/OPTIONS.java new file mode 100644 index 0000000000..5a18123bcd --- /dev/null +++ b/src/org/nutz/mvc/annotation/OPTIONS.java @@ -0,0 +1,18 @@ +package org.nutz.mvc.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; + +/** + * 描述一个入口函数,是不是仅仅响应 OPTIONS 请求 + * + * @author thomas(ywjno.dev@gmail.com) + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +@Documented +public @interface OPTIONS { +} diff --git a/src/org/nutz/mvc/annotation/Ok.java b/src/org/nutz/mvc/annotation/Ok.java index 241e0702ec..dc48320588 100644 --- a/src/org/nutz/mvc/annotation/Ok.java +++ b/src/org/nutz/mvc/annotation/Ok.java @@ -6,6 +6,18 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * 用法 + * + * @ok("json") + * 返回有值字段json + * + * @ok("json:full") + * 返回所有字段 + * + * @ok("json:{locked:'password|createAt|salt',ignoreNull:true}") + * 忽略password和createAt属性,忽略空属 + */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.TYPE}) @Documented diff --git a/src/org/nutz/mvc/annotation/PATCH.java b/src/org/nutz/mvc/annotation/PATCH.java new file mode 100644 index 0000000000..7638552730 --- /dev/null +++ b/src/org/nutz/mvc/annotation/PATCH.java @@ -0,0 +1,18 @@ +package org.nutz.mvc.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; + +/** + * 描述一个入口函数,是不是仅仅响应 PATCH 请求 + * + * @author thomas(ywjno.dev@gmail.com) + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +@Documented +public @interface PATCH { +} diff --git a/src/org/nutz/mvc/config/AbstractNutConfig.java b/src/org/nutz/mvc/config/AbstractNutConfig.java index a2ef360a40..65bedb14b2 100644 --- a/src/org/nutz/mvc/config/AbstractNutConfig.java +++ b/src/org/nutz/mvc/config/AbstractNutConfig.java @@ -1,5 +1,6 @@ package org.nutz.mvc.config; +import java.io.File; import java.util.Enumeration; import java.util.LinkedList; import java.util.List; @@ -79,7 +80,11 @@ public String getAppRoot() { String webinf = getServletContext().getRealPath("/WEB-INF/"); if (webinf == null) { log.info("/WEB-INF/ not Found?!"); - return ""; + if (new File("src/main/webapp").exists()) + return new File("src/main/webapp").getAbsolutePath(); + if (new File("src/main/resources/webapp").exists()) + return new File("src/main/resources/webapp").getAbsolutePath(); + return "./webapp"; } String root = getServletContext().getRealPath("/").replace('\\', '/'); if (root.endsWith("/")) diff --git a/src/org/nutz/mvc/filter/CrossOriginFilter.java b/src/org/nutz/mvc/filter/CrossOriginFilter.java index e525d106fa..a879216d2f 100644 --- a/src/org/nutz/mvc/filter/CrossOriginFilter.java +++ b/src/org/nutz/mvc/filter/CrossOriginFilter.java @@ -23,7 +23,7 @@ public class CrossOriginFilter implements ActionFilter { protected String credentials; public CrossOriginFilter() { - this("*", "get, post, put, delete, options", "origin, content-type, accept", "true"); + this("*", "GET, POST, PUT, DELETE, OPTIONS, PATCH", "Origin, Content-Type, Accept, X-Requested-With", "true"); } public CrossOriginFilter(String origin, String methods, String headers, String credentials) { diff --git a/src/org/nutz/mvc/i18n/LocalizationManager.java b/src/org/nutz/mvc/i18n/LocalizationManager.java new file mode 100644 index 0000000000..13ed1b82a6 --- /dev/null +++ b/src/org/nutz/mvc/i18n/LocalizationManager.java @@ -0,0 +1,18 @@ +package org.nutz.mvc.i18n; + +import java.util.Set; + +import org.nutz.mvc.impl.NutMessageMap; + +public interface LocalizationManager { + + void setDefaultLocal(String local); + + String getDefaultLocal(); + + Set getLocals(); + + NutMessageMap getMessageMap(String local); + + String getMessage(String local, String key); +} diff --git a/src/org/nutz/mvc/impl/ActionInvoker.java b/src/org/nutz/mvc/impl/ActionInvoker.java index a2a3040332..2b6ba93112 100644 --- a/src/org/nutz/mvc/impl/ActionInvoker.java +++ b/src/org/nutz/mvc/impl/ActionInvoker.java @@ -1,92 +1,79 @@ package org.nutz.mvc.impl; -import java.util.HashMap; -import java.util.Map; +import java.util.ArrayList; +import java.util.List; -import javax.servlet.http.HttpServletRequest; - -import org.nutz.lang.Lang; -import org.nutz.lang.Strings; import org.nutz.log.Log; import org.nutz.log.Logs; import org.nutz.mvc.ActionChain; import org.nutz.mvc.ActionContext; +import org.nutz.mvc.ActionInfo; +import org.nutz.mvc.RequestMatcher; +import org.nutz.mvc.impl.reqmatcher.ApiVersionRequestMatcher; +import org.nutz.mvc.impl.reqmatcher.DefaultRequestMatcher; /** * 根据 HTTP 请求的方法 (GET|POST|PUT|DELETE) 来调用响应的动作链 * * @author zozoh(zozohtnt@gmail.com) + * @author wendal(wendal1985@gmail.com) */ public class ActionInvoker { private static final Log log = Logs.get(); - - private ActionChain defaultChain; - private Map chainMap; + protected List matchers; + + protected DefaultRequestMatcher dft = new DefaultRequestMatcher(); public ActionInvoker() { - chainMap = new HashMap(); + matchers = new ArrayList(2); + matchers.add(new ApiVersionRequestMatcher()); + matchers.add(dft); } - /** - * 增加 ActionChain - * - * @param httpMethod - * HTTP 的请求方法 (GET|POST|PUT|DELETE),如果为空,则会抛错 - * @param chain - * 动作链 - */ - public void addChain(String httpMethod, ActionChain chain) { - if (Strings.isBlank(httpMethod)) - throw Lang.makeThrow("chain need a valid HTTP Method, but is is '%s'", httpMethod); - ActionChain old = chainMap.put(httpMethod.toUpperCase(), chain); - if (old != null) { - log.warnf("Duplicate @At mapping with same HttpMethod"); + public void add(String path, ActionInfo ai, ActionChain chain) { + for (RequestMatcher matcher : matchers) { + matcher.add(path, ai, chain); } } - public void setDefaultChain(ActionChain defaultChain) { - this.defaultChain = defaultChain; + public ActionChain getActionChain(ActionContext ac) { + for (RequestMatcher matcher : matchers) { + ActionChain chain = matcher.match(ac); + if (chain != null) { + ac.set("nutz.mvc.current.chain", chain); + return chain; + } + } + if (log.isDebugEnabled()) + log.debugf("Not chain for req (path=%s, method=%s)", ac.getPath(), ac.getRequest().getMethod()); + return null; } - /** - * 根据动作链上下文对象,调用一个相应的动作链 - * - * @param ac - * 动作链上下文 - * @return true- 成功的找到一个动作链并执行。 false- 没有找到动作链 - */ public boolean invoke(ActionContext ac) { - ActionChain chain = getActionChain(ac); - if (chain == null) { - if (log.isDebugEnabled()) - log.debugf("Not chain for req (path=%s, method=%s)", ac.getPath(), ac.getRequest().getMethod()); - return false; - } + ActionChain chain = (ActionChain) ac.remove("nutz.mvc.current.chain"); chain.doChain(ac); return ac.getBoolean(ActionContext.AC_DONE, true); } - public ActionChain getActionChain(ActionContext ac) { - String httpMethod = ""; - if (!chainMap.isEmpty()) { - HttpServletRequest req = ac.getRequest(); - httpMethod = Strings.sNull(req.getMethod(), "GET").toUpperCase(); - ActionChain chain = chainMap.get(httpMethod); - // 找到了特殊HTTP方法的处理动作链 - if (null != chain) { - return chain; - } - } - // 这个 URL 所有的HTTP方法用统一的动作链处理 - if (null != defaultChain) { - return defaultChain; - } - if (chainMap.size() != 0 && log.isDebugEnabled()) { - log.debugf("Path=[%s] available methods%s but request [%s], using the wrong http method?", ac.getPath(), chainMap.keySet(), httpMethod); - } - // 否则将认为不能处理 - return null; + // --------------------------------------------------------------------------- + // 为了兼容老的ActionInvoker + public void addChain(String httpMethod, ActionChain chain) { + dft.setDefaultChain(chain); + } + + public void setDefaultChain(ActionChain defaultChain) { + dft.setDefaultChain(defaultChain); + } + + // --------------------------------------------------------------------------- + // 预留2个方法吧 + public void setMatchers(List matchers) { + this.matchers = matchers; + } + + public List getMatchers() { + return matchers; } } diff --git a/src/org/nutz/mvc/impl/Loadings.java b/src/org/nutz/mvc/impl/Loadings.java index e71c522cd5..30bfeffa4e 100644 --- a/src/org/nutz/mvc/impl/Loadings.java +++ b/src/org/nutz/mvc/impl/Loadings.java @@ -42,7 +42,9 @@ import org.nutz.mvc.annotation.Filters; import org.nutz.mvc.annotation.GET; import org.nutz.mvc.annotation.Modules; +import org.nutz.mvc.annotation.OPTIONS; import org.nutz.mvc.annotation.Ok; +import org.nutz.mvc.annotation.PATCH; import org.nutz.mvc.annotation.POST; import org.nutz.mvc.annotation.PUT; import org.nutz.mvc.annotation.PathMap; @@ -60,7 +62,7 @@ public static ActionInfo createInfo(Class type) { evalPathMap(ai, Mirror.getAnnotationDeep(type, PathMap.class)); evalOk(ai, Mirror.getAnnotationDeep(type, Ok.class)); evalFail(ai, Mirror.getAnnotationDeep(type, Fail.class)); - evalAt(ai, Mirror.getAnnotationDeep(type, At.class), type.getSimpleName()); + evalAt(ai, Mirror.getAnnotationDeep(type, At.class), type.getSimpleName(), false); evalActionChainMaker(ai, Mirror.getAnnotationDeep(type, Chain.class)); evalModule(ai, type); if (Mvcs.DISPLAY_METHOD_LINENUMBER) { @@ -85,16 +87,13 @@ public static ActionInfo createInfo(Method method) { evalOk(ai, Mirror.getAnnotationDeep(method, Ok.class)); evalFail(ai, Mirror.getAnnotationDeep(method, Fail.class)); evalHttpMethod(ai, method, Mirror.getAnnotationDeep(method, At.class)); - evalAt(ai, Mirror.getAnnotationDeep(method, At.class), method.getName()); + evalAt(ai, Mirror.getAnnotationDeep(method, At.class), method.getName(), true); evalActionChainMaker(ai, Mirror.getAnnotationDeep(method, Chain.class)); ai.setMethod(method); return ai; } - private static EntryDeterminer determiner = null; - public static Set> scanModules(Ioc ioc, Class mainModule, EntryDeterminer determiner) { - Loadings.determiner = determiner; Modules ann = mainModule.getAnnotation(Modules.class); boolean scan = null == ann ? true : ann.scanPackage(); // 准备扫描列表 @@ -136,7 +135,7 @@ public static Set> scanModules(Ioc ioc, Class mainModule, EntryDeter Collection> col = ms.scan(); if (null != col) for (Class type : col) { - if (isModule(type)) { + if (isModule(type, determiner)) { modules.add(type); } } @@ -145,7 +144,7 @@ public static Set> scanModules(Ioc ioc, Class mainModule, EntryDeter // 扫描包,扫描出的类直接计入结果 if (ann.packages() != null && ann.packages().length > 0) { for (String packageName : ann.packages()) { - scanModuleInPackage(modules, packageName); + scanModuleInPackage(modules, packageName, determiner); } } } @@ -167,11 +166,11 @@ public static Set> scanModules(Ioc ioc, Class mainModule, EntryDeter for (Class type : forScans) { // 扫描子包 if (scan) { - scanModuleInPackage(modules, type.getPackage().getName()); + scanModuleInPackage(modules, type.getPackage().getName(), determiner); } // 仅仅加载自己 else { - if (isModule(type)) { + if (isModule(type, determiner)) { if (log.isDebugEnabled()) log.debugf(" > Found @At : '%s'", type.getName()); modules.add(type); @@ -183,22 +182,26 @@ public static Set> scanModules(Ioc ioc, Class mainModule, EntryDeter return modules; } - protected static void scanModuleInPackage(Set> modules, String packageName) { + public static void scanModuleInPackage(Set> modules, String packageName, EntryDeterminer determiner) { if (log.isDebugEnabled()) log.debugf(" > scan '%s'", packageName); List> subs = Scans.me().scanPackage(packageName); - checkModule(modules, subs); + checkModule(modules, subs, determiner); + } + + public static void scanModuleInPackage(Set> modules, String packageName) { + scanModuleInPackage(modules, packageName, new NutEntryDeterminer()); } /** * @param modules * @param subs */ - private static void checkModule(Set> modules, List> subs) { + private static void checkModule(Set> modules, List> subs, EntryDeterminer determiner) { for (Class sub : subs) { try { - if (isModule(sub)) { + if (isModule(sub, determiner)) { if (log.isDebugEnabled()) log.debugf(" >> add '%s'", sub.getName()); modules.add(sub); @@ -221,6 +224,10 @@ public static void evalHttpMethod(ActionInfo ai, Method method, At at) { ai.getHttpMethods().add("PUT"); if (Mirror.getAnnotationDeep(method, DELETE.class) != null) ai.getHttpMethods().add("DELETE"); + if (Mirror.getAnnotationDeep(method, OPTIONS.class) != null) + ai.getHttpMethods().add("OPTIONS"); + if (Mirror.getAnnotationDeep(method, PATCH.class) != null) + ai.getHttpMethods().add("PATCH"); if (at != null) { for (String m : at.methods()) ai.getHttpMethods().add(m.toUpperCase()); @@ -233,7 +240,7 @@ public static void evalActionChainMaker(ActionInfo ai, Chain cb) { } } - public static void evalAt(ActionInfo ai, At at, String def) { + public static void evalAt(ActionInfo ai, At at, String def, boolean isMethod) { if (null != at) { if (null == at.value() || at.value().length == 0) { ai.setPaths(Lang.array("/" + def.toLowerCase())); @@ -245,8 +252,8 @@ public static void evalAt(ActionInfo ai, At at, String def) { ai.setPathKey(at.key()); if (at.top()) ai.setPathTop(true); - } else if (!Lang.isEmpty(ai.getHttpMethods())) { - // 没有@At但有GET POST等 + } else if (isMethod) { + // 由于EntryDeterminer机制的存在,action方法上可能没有@At,这时候给一个默认的入口路径 ai.setPaths(Lang.array("/" + def.toLowerCase())); } } @@ -335,7 +342,7 @@ public static T evalObj(NutConfig config, Class type, String[] args) { return Mirror.me(type).born((Object[]) args); } - public static boolean isModule(Class classZ) { + public static boolean isModule(Class classZ, EntryDeterminer determiner) { int classModify = classZ.getModifiers(); if (!Modifier.isPublic(classModify) || Modifier.isAbstract(classModify) @@ -346,4 +353,8 @@ public static boolean isModule(Class classZ) { return true; return false; } + + public static boolean isModule(Class classZ) { + return isModule(classZ, new NutEntryDeterminer()); + } } diff --git a/src/org/nutz/mvc/impl/NutEntryDeterminer.java b/src/org/nutz/mvc/impl/NutEntryDeterminer.java index cd0fc66e90..aa04ff3c45 100644 --- a/src/org/nutz/mvc/impl/NutEntryDeterminer.java +++ b/src/org/nutz/mvc/impl/NutEntryDeterminer.java @@ -8,6 +8,8 @@ import org.nutz.mvc.annotation.At; import org.nutz.mvc.annotation.DELETE; import org.nutz.mvc.annotation.GET; +import org.nutz.mvc.annotation.OPTIONS; +import org.nutz.mvc.annotation.PATCH; import org.nutz.mvc.annotation.POST; import org.nutz.mvc.annotation.PUT; @@ -39,7 +41,14 @@ public class NutEntryDeterminer implements EntryDeterminer { public boolean isEntry(Class module, Method method) { if (!Modifier.isPublic(method.getModifiers()) || method.isBridge()) return false; - return Mirror.isAnnotationExists(method, At.class, GET.class, POST.class, PUT.class, DELETE.class); + return Mirror.isAnnotationExists(method, + At.class, + GET.class, + POST.class, + PUT.class, + DELETE.class, + OPTIONS.class, + PATCH.class); } } diff --git a/src/org/nutz/mvc/impl/NutLoading.java b/src/org/nutz/mvc/impl/NutLoading.java index 70f7a21585..b23f1e4d5e 100644 --- a/src/org/nutz/mvc/impl/NutLoading.java +++ b/src/org/nutz/mvc/impl/NutLoading.java @@ -2,13 +2,8 @@ import java.io.File; import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; -import java.util.Properties; -import java.util.Set; import org.nutz.Nutz; import org.nutz.ioc.Ioc; @@ -24,18 +19,7 @@ import org.nutz.lang.util.Context; import org.nutz.log.Log; import org.nutz.log.Logs; -import org.nutz.mvc.ActionChainMaker; -import org.nutz.mvc.ActionInfo; -import org.nutz.mvc.EntryDeterminer; -import org.nutz.mvc.Loading; -import org.nutz.mvc.LoadingException; -import org.nutz.mvc.MessageLoader; -import org.nutz.mvc.Mvcs; -import org.nutz.mvc.NutConfig; -import org.nutz.mvc.SessionProvider; -import org.nutz.mvc.Setup; -import org.nutz.mvc.UrlMapping; -import org.nutz.mvc.ViewMaker; +import org.nutz.mvc.*; import org.nutz.mvc.annotation.ChainBy; import org.nutz.mvc.annotation.Determiner; import org.nutz.mvc.annotation.IocBy; @@ -119,6 +103,9 @@ public UrlMapping load(NutConfig config) { * 执行用户自定义 Setup */ evalSetup(config, mainModule); + + // 应用完成后执行用户自定义的 CommandLineRunner + callRunners(ioc); } catch (Exception e) { if (log.isErrorEnabled()) @@ -181,7 +168,7 @@ protected UrlMapping evalUrlMapping(NutConfig config, Class mainModule, Ioc i * 准备要加载的模块列表 */ // TODO 为什么用Set呢? 用List不是更快吗? - Set> modules = Loadings.scanModules(ioc, mainModule, determiner); + Set> modules = getModuleClasses(ioc, mainModule, determiner); if (modules.isEmpty()) { if (log.isWarnEnabled()) @@ -195,12 +182,12 @@ protected UrlMapping evalUrlMapping(NutConfig config, Class mainModule, Ioc i if (log.isDebugEnabled()) log.debugf("Use %s as EntryMethodDeterminer", determiner.getClass().getName()); for (Class module : modules) { - ActionInfo moduleInfo = Loadings.createInfo(module).mergeWith(mainInfo); + ActionInfo moduleInfo = Loadings.createInfo(module).mergeWith(mainInfo, true); for (Method method : module.getMethods()) { if (!determiner.isEntry(module, method)) continue; // 增加到映射中 - ActionInfo info = Loadings.createInfo(method).mergeWith(moduleInfo); + ActionInfo info = Loadings.createInfo(method).mergeWith(moduleInfo, false); info.setViewMakers(makers); mapping.add(maker, info, config); atMethods++; @@ -220,7 +207,7 @@ protected UrlMapping evalUrlMapping(NutConfig config, Class mainModule, Ioc i } else { log.infof("Found %d module methods", atMethods); } - + config.setUrlMapping(mapping); config.setActionChainMaker(maker); config.setViewMakers(makers); @@ -276,7 +263,7 @@ protected void evalSetup(NutConfig config, Class mainModule) throws Exception Setup setup = Loadings.evalObj(config, sb.value(), sb.args()); config.setAttributeIgnoreNull(Setup.class.getName(), setup); setup.init(config); - } else if (config.getIoc() != null && config.getIoc().has(Setup.IOCNAME)) { + } else if (config.getIoc() != null) { String[] names = config.getIoc().getNames(); Arrays.sort(names); boolean flag = true; @@ -287,8 +274,8 @@ protected void evalSetup(NutConfig config, Class mainModule) throws Exception if (log.isInfoEnabled()) log.info("Setup application..."); } - log.debug("load Setup from Ioc by name=" + Setup.IOCNAME); - Setup setup = config.getIoc().get(Setup.class, Setup.IOCNAME); + log.debug("load Setup from Ioc by name=" + name); + Setup setup = config.getIoc().get(Setup.class, name); config.setAttributeIgnoreNull(Setup.class.getName(), setup); setup.init(config); } @@ -300,6 +287,26 @@ protected void evalSetup(NutConfig config, Class mainModule) throws Exception } } + protected void callRunners(Ioc ioc){ + if(Objects.nonNull(ioc)){ + String[] names = ioc.getNamesByType(CommandLineRunner.class); + Arrays.sort(names); + for (String beanName : names) { + CommandLineRunner commandLineRunner = ioc.get(CommandLineRunner.class, beanName); + callRunner(commandLineRunner); + } + } + } + + private void callRunner(CommandLineRunner runner){ + try { + runner.run(); + } + catch (Exception ex) { + throw new IllegalStateException("Failed to execute CommandLineRunner", ex); + } + } + protected void evalLocalization(NutConfig config, Class mainModule) { Localization lc = mainModule.getAnnotation(Localization.class); if (null != lc) { @@ -347,15 +354,14 @@ protected ViewMaker[] createViewMakers(Class mainModule, Ioc ioc) throws Exce makers.add(Mirror.me(vms.value()[i]).born()); } } - } else { - if (ioc != null) { - String[] names = ioc.getNames(); - Arrays.sort(names); - for (String name : ioc.getNames()) { - if (name != null && name.startsWith(ViewMaker.IOCNAME)) { - log.debug("add ViewMaker from Ioc by name=" + name); - makers.add(ioc.get(ViewMaker.class, name)); - } + } + if (ioc != null) { + String[] names = ioc.getNames(); + Arrays.sort(names); + for (String name : ioc.getNames()) { + if (name != null && name.startsWith(ViewMaker.IOCNAME)) { + log.debug("add ViewMaker from Ioc by name=" + name); + makers.add(ioc.get(ViewMaker.class, name)); } } } @@ -446,4 +452,7 @@ public void depose(NutConfig config) { log.infof("Nutz.Mvc[%s] is down in %sms", config.getAppName(), sw.getDuration()); } + protected Set> getModuleClasses(Ioc ioc, Class mainModule, EntryDeterminer determiner) { + return Loadings.scanModules(ioc, mainModule, determiner); + } } diff --git a/src/org/nutz/mvc/impl/ResourceBundleMessageLoader.java b/src/org/nutz/mvc/impl/ResourceBundleMessageLoader.java new file mode 100644 index 0000000000..74c2678dfd --- /dev/null +++ b/src/org/nutz/mvc/impl/ResourceBundleMessageLoader.java @@ -0,0 +1,66 @@ +package org.nutz.mvc.impl; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import org.nutz.lang.util.NutMap; +import org.nutz.log.Log; +import org.nutz.log.Logs; +import org.nutz.mvc.MessageLoader; +import org.nutz.mvc.Mvcs; +import org.nutz.resource.NutResource; +import org.nutz.resource.Scans; + +/** + * 使用类似于java.util.ResourceBundle的规则加载本地化数据 + * @author wendal(wendal1985@gmail.com) + * + */ +public class ResourceBundleMessageLoader implements MessageLoader { + + private static final Log log = Logs.get(); + + @Override + public Map> load(String refer) { + Map> re = new HashMap>(); + re.put(Mvcs.DEFAULT_MSGS, new NutMap()); + List allnrs = Scans.me().scan(refer, "^.+[.]properties$"); + if (log.isDebugEnabled()) + log.debugf("Load Messages in %s resource : [%s]", allnrs.size(), allnrs); + for (NutResource nr : allnrs) { + try { + String name = nr.getName(); + if (name.contains("/")) { + name = name.substring(name.lastIndexOf('/') + 1); + } + if (name.contains("\\")) { + name = name.substring(name.lastIndexOf('\\') + 1); + } + name = name.substring(0, name.length() - ".properties".length()); + String langType = Mvcs.DEFAULT_MSGS; + if (name.contains("_")) { + langType = name.substring(name.indexOf('_')+1); + } + Properties properties = new Properties(); + properties.load(nr.getInputStream()); + NutMap msgs = (NutMap) re.get(langType); + if (msgs == null) { + if (log.isDebugEnabled()) { + log.debug("add Message Locale : " + langType); + } + msgs = new NutMap(); + re.put(langType, msgs); + } + for (Map.Entry en : properties.entrySet()) { + msgs.put(String.valueOf(en.getKey()), String.valueOf(en.getValue())); + } + } catch (Exception e) { + throw new RuntimeException("error when reading " + nr.getName(), e); + } + } + return re; + } + +} diff --git a/src/org/nutz/mvc/impl/UrlMappingImpl.java b/src/org/nutz/mvc/impl/UrlMappingImpl.java index 3996de7bda..d685790f58 100644 --- a/src/org/nutz/mvc/impl/UrlMappingImpl.java +++ b/src/org/nutz/mvc/impl/UrlMappingImpl.java @@ -64,19 +64,21 @@ public void add(ActionChainMaker maker, ActionInfo ai, NutConfig config) { root.add(path, invoker); // 记录一下方法与 url 的映射 config.getAtMap().addMethod(path, ai.getMethod()); - } else if (!ai.isForSpecialHttpMethod()) { - log.warnf("Duplicate @At mapping ? path=" + path); } + //else if (!ai.isForSpecialHttpMethod()) { + // log.warnf("Duplicate @At mapping ? path=" + path); + //} // 将动作链,根据特殊的 HTTP 方法,保存到调用者内部 - if (ai.isForSpecialHttpMethod()) { - for (String httpMethod : ai.getHttpMethods()) - invoker.addChain(httpMethod, chain); - } + //if (ai.isForSpecialHttpMethod()) { + // for (String httpMethod : ai.getHttpMethods()) + // invoker.addChain(httpMethod, chain); + //} // 否则,将其设置为默认动作链 - else { - invoker.setDefaultChain(chain); - } + //else { + // invoker.setDefaultChain(chain); + //} + invoker.add(path, ai, chain); } printActionMapping(ai); diff --git a/src/org/nutz/mvc/impl/contextCollector/AtCollector.java b/src/org/nutz/mvc/impl/contextCollector/AtCollector.java new file mode 100644 index 0000000000..bd08ddade8 --- /dev/null +++ b/src/org/nutz/mvc/impl/contextCollector/AtCollector.java @@ -0,0 +1,30 @@ +package org.nutz.mvc.impl.contextCollector; + +import org.nutz.lang.Lang; +import org.nutz.lang.util.Context; +import org.nutz.mvc.Mvcs; +import org.nutz.mvc.ViewContextCollector; +import org.nutz.mvc.config.AtMap; + +import javax.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.Map; + +/** + * 路径入口收集 + */ +public class AtCollector implements ViewContextCollector { + @Override + public Context collect(HttpServletRequest req, Object obj) { + Map u = new HashMap(); + AtMap at = Mvcs.getAtMap(); + if (at != null) { + for (Object o : at.keys()) { + String key = (String) o; + u.put(key, at.get(key)); + } + return Lang.context("u", u); + } + return null; + } +} diff --git a/src/org/nutz/mvc/impl/contextCollector/AttrCollector.java b/src/org/nutz/mvc/impl/contextCollector/AttrCollector.java new file mode 100644 index 0000000000..ccc3cac064 --- /dev/null +++ b/src/org/nutz/mvc/impl/contextCollector/AttrCollector.java @@ -0,0 +1,29 @@ +package org.nutz.mvc.impl.contextCollector; + +import org.nutz.lang.Lang; +import org.nutz.lang.util.Context; +import org.nutz.mvc.ViewContextCollector; + +import javax.servlet.http.HttpServletRequest; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; + +/** + * 请求对象的属性列表 + */ +public class AttrCollector implements ViewContextCollector { + @Override + public Context collect(HttpServletRequest req, Object obj) { + Map req_attr = new HashMap(); + for (Enumeration en = req.getAttributeNames(); en.hasMoreElements();) { + String tem = en.nextElement(); + if (!tem.startsWith("$")) + req_attr.put(tem, req.getAttribute(tem)); + } + Context ctx = Lang.context(); + ctx.set("a", req_attr);// 兼容最初的写法 + ctx.set("req_attr", req_attr); + return ctx; + } +} diff --git a/src/org/nutz/mvc/impl/contextCollector/ParamCollector.java b/src/org/nutz/mvc/impl/contextCollector/ParamCollector.java new file mode 100644 index 0000000000..acea13933c --- /dev/null +++ b/src/org/nutz/mvc/impl/contextCollector/ParamCollector.java @@ -0,0 +1,29 @@ +package org.nutz.mvc.impl.contextCollector; + +import org.nutz.lang.Lang; +import org.nutz.lang.util.Context; +import org.nutz.mvc.ViewContextCollector; + +import javax.servlet.http.HttpServletRequest; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +/** + * 收集请求参数 + */ +public class ParamCollector implements ViewContextCollector { + @Override + public Context collect(HttpServletRequest req, Object obj) { + Map p = new HashMap(); + Context ctx = Lang.context(); + for (Object o : Lang.enum2collection(req.getParameterNames(), new ArrayList())) { + String key = (String) o; + String value = req.getParameter(key); + p.put(key, value); + ctx.set(key, value);// 以支持直接获取请求参数 + } + ctx.set("p", p); + return ctx; + } +} diff --git a/src/org/nutz/mvc/impl/contextCollector/PathargsCollector.java b/src/org/nutz/mvc/impl/contextCollector/PathargsCollector.java new file mode 100644 index 0000000000..cf14e8c86b --- /dev/null +++ b/src/org/nutz/mvc/impl/contextCollector/PathargsCollector.java @@ -0,0 +1,23 @@ +package org.nutz.mvc.impl.contextCollector; + +import org.nutz.lang.Lang; +import org.nutz.lang.util.Context; +import org.nutz.mvc.ActionContext; +import org.nutz.mvc.Mvcs; +import org.nutz.mvc.ViewContextCollector; + +import javax.servlet.http.HttpServletRequest; + +/** + * 路径参数收集 + */ +public class PathargsCollector implements ViewContextCollector { + @Override + public Context collect(HttpServletRequest req, Object obj) { + ActionContext ac = Mvcs.getActionContext(); + if (ac != null){ + return Lang.context("pathargs", Mvcs.getActionContext().getPathArgs()); + } + return null; + } +} diff --git a/src/org/nutz/mvc/impl/contextCollector/RefCollector.java b/src/org/nutz/mvc/impl/contextCollector/RefCollector.java new file mode 100644 index 0000000000..ae109f087b --- /dev/null +++ b/src/org/nutz/mvc/impl/contextCollector/RefCollector.java @@ -0,0 +1,32 @@ +package org.nutz.mvc.impl.contextCollector; + +import org.nutz.lang.Lang; +import org.nutz.lang.util.Context; +import org.nutz.mvc.Mvcs; +import org.nutz.mvc.ViewContextCollector; + +import javax.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.Map; + +/** + * 被转换过的值 + */ +public class RefCollector implements ViewContextCollector { + @Override + public Context collect(HttpServletRequest req, Object obj) { + Object ref = Mvcs.getActionContext().getReferObject(); + if(ref == null) { + return null; + } + Map p = new HashMap(); + if (ref instanceof Map) { + p.putAll((Map) ref); + } else { + p.put("$ref", ref); + } + Context ctx = Lang.context(); + ctx.set("r", p); + return ctx; + } +} diff --git a/src/org/nutz/mvc/impl/contextCollector/ReturnCollector.java b/src/org/nutz/mvc/impl/contextCollector/ReturnCollector.java new file mode 100644 index 0000000000..75b23a5fe7 --- /dev/null +++ b/src/org/nutz/mvc/impl/contextCollector/ReturnCollector.java @@ -0,0 +1,21 @@ +package org.nutz.mvc.impl.contextCollector; + +import org.nutz.lang.Lang; +import org.nutz.lang.util.Context; +import org.nutz.mvc.ViewContextCollector; +import org.nutz.mvc.impl.processor.ViewProcessor; + +import javax.servlet.http.HttpServletRequest; + +/** + * 收集返回值 + */ +public class ReturnCollector implements ViewContextCollector { + @Override + public Context collect(HttpServletRequest req, Object obj) { + if (null != obj) { + return Lang.context(ViewProcessor.DEFAULT_ATTRIBUTE, obj); + } + return null; + } +} diff --git a/src/org/nutz/mvc/impl/contextCollector/ServletContextCollector.java b/src/org/nutz/mvc/impl/contextCollector/ServletContextCollector.java new file mode 100644 index 0000000000..b068ea4c22 --- /dev/null +++ b/src/org/nutz/mvc/impl/contextCollector/ServletContextCollector.java @@ -0,0 +1,18 @@ +package org.nutz.mvc.impl.contextCollector; + +import org.nutz.lang.util.Context; +import org.nutz.mvc.Loading; +import org.nutz.mvc.Mvcs; +import org.nutz.mvc.ViewContextCollector; + +import javax.servlet.http.HttpServletRequest; + +/** + * 复制全局的上下文对象 + */ +public class ServletContextCollector implements ViewContextCollector { + @Override + public Context collect(HttpServletRequest req, Object obj) { + return (Context) Mvcs.getServletContext().getAttribute(Loading.CONTEXT_NAME); + } +} diff --git a/src/org/nutz/mvc/impl/contextCollector/SessionCollector.java b/src/org/nutz/mvc/impl/contextCollector/SessionCollector.java new file mode 100644 index 0000000000..c0222b1035 --- /dev/null +++ b/src/org/nutz/mvc/impl/contextCollector/SessionCollector.java @@ -0,0 +1,38 @@ +package org.nutz.mvc.impl.contextCollector; + +import org.nutz.lang.Lang; +import org.nutz.lang.util.Context; +import org.nutz.mvc.Mvcs; +import org.nutz.mvc.ViewContextCollector; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; + +/** + * session变量收集 + */ +public class SessionCollector implements ViewContextCollector { + @Override + public Context collect(HttpServletRequest req, Object obj) { + try { + HttpSession session = Mvcs.getHttpSession(false); + if (session != null) { + Map session_attr = new HashMap(); + for (Enumeration en = session.getAttributeNames(); en.hasMoreElements();) { + String tem = en.nextElement(); + session_attr.put(tem, session.getAttribute(tem)); + } + return Lang.context("session_attr", session_attr); + } + } + catch (Throwable e) { + // noop + } + + return null; + + } +} diff --git a/src/org/nutz/mvc/impl/contextCollector/SharedCollector.java b/src/org/nutz/mvc/impl/contextCollector/SharedCollector.java new file mode 100644 index 0000000000..72986ee6e2 --- /dev/null +++ b/src/org/nutz/mvc/impl/contextCollector/SharedCollector.java @@ -0,0 +1,44 @@ +package org.nutz.mvc.impl.contextCollector; + +import org.nutz.ioc.Ioc; +import org.nutz.lang.Lang; +import org.nutz.lang.util.Context; +import org.nutz.mvc.Mvcs; +import org.nutz.mvc.ViewContextCollector; + +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.http.HttpServletRequest; + +/** + * 共享变量收集器 + */ +public class SharedCollector implements ViewContextCollector { + + protected List list; + protected boolean hasItem; + + public Context collect(HttpServletRequest req, Object obj) { + Ioc ioc = Mvcs.getIoc(); + Context ctx = Lang.context(); + if (ioc == null) + return ctx; + if (list == null) { + List tmp = new ArrayList(); + String[] names = ioc.getNamesByType(ViewContextCollector.class); + for (String name : names) { + ViewContextCollector vcc = ioc.get(ViewContextCollector.class, name); + tmp.add(vcc); + hasItem = true; + } + list = tmp; + } + if (hasItem) { + for (ViewContextCollector vcc : list) { + ctx.putAll(vcc.collect(req, obj)); + } + } + return ctx; + } +} diff --git a/src/org/nutz/mvc/impl/processor/AdaptorProcessor.java b/src/org/nutz/mvc/impl/processor/AdaptorProcessor.java index 09df3dfc26..e7d9fc154a 100644 --- a/src/org/nutz/mvc/impl/processor/AdaptorProcessor.java +++ b/src/org/nutz/mvc/impl/processor/AdaptorProcessor.java @@ -2,6 +2,8 @@ import java.util.List; +import javax.servlet.http.HttpServletRequest; + import org.nutz.mvc.*; import org.nutz.mvc.adaptor.PairAdaptor; @@ -22,10 +24,16 @@ public void init(NutConfig config, ActionInfo ai) throws Throwable { public void process(ActionContext ac) throws Throwable { List phArgs = ac.getPathArgs(); + HttpServletRequest req = ac.getRequest(); + if (ac.getReferObject() != null) + req.setAttribute(ActionContext.REFER_OBJECT, ac.getReferObject()); Object[] args = adaptor.adapt(ac.getServletContext(), - ac.getRequest(), + req, ac.getResponse(), phArgs.toArray(new String[phArgs.size()])); + Object referObject = req.getAttribute(ActionContext.REFER_OBJECT); + ac.setReferObject(referObject); + req.removeAttribute(ActionContext.REFER_OBJECT); ac.setMethodArgs(args); doNext(ac); } diff --git a/src/org/nutz/mvc/impl/reqmatcher/ApiVersionRequestMatcher.java b/src/org/nutz/mvc/impl/reqmatcher/ApiVersionRequestMatcher.java new file mode 100644 index 0000000000..a4decbb071 --- /dev/null +++ b/src/org/nutz/mvc/impl/reqmatcher/ApiVersionRequestMatcher.java @@ -0,0 +1,64 @@ +package org.nutz.mvc.impl.reqmatcher; + +import java.util.HashMap; +import java.util.Map; + +import org.nutz.mvc.ActionChain; +import org.nutz.mvc.ActionContext; +import org.nutz.mvc.ActionInfo; +import org.nutz.mvc.RequestMatcher; +import org.nutz.mvc.annotation.ApiVersion; + +public class ApiVersionRequestMatcher implements RequestMatcher { + + protected int index = -1; + protected Map matchers; + protected boolean keepPathArg; + + public void add(String path, ActionInfo ai, ActionChain chain) { + ApiVersion panno = ai.getModuleType().getAnnotation(ApiVersion.class); + if (panno != null) { + ApiVersion anno = ai.getMethod().getAnnotation(ApiVersion.class); + if (anno == null) + anno = panno; + String[] namedPathArgs = ai.getNamedPathArgs(); + if (namedPathArgs != null) { + for (int i = 0; i < namedPathArgs.length; i++) { + if ("version".equals(namedPathArgs[i])) { + this.index = i; + if (matchers == null) + matchers = new HashMap(); + DefaultRequestMatcher matcher = matchers.get(anno.value()); + if (matcher == null) { + matcher = new DefaultRequestMatcher(); + matchers.put(anno.value(), matcher); + } + matcher.add(path, ai, chain); + if (anno.keepPathArg()) + keepPathArg = true; + return; // 直接搞定 + } + } + } + } + } + + public ActionChain match(ActionContext ctx) { + if (matchers == null || ctx.getPathArgs().size() <= index) + return null; + String version = ctx.getPathArgs().get(index); + if (version == null) + return null; + DefaultRequestMatcher matcher = matchers.get(version); + if (matcher != null) { + ActionChain chain = matcher.match(ctx); + if (chain != null) { + if (!keepPathArg) + ctx.getPathArgs().remove(index); + return chain; + } + } + return null; + } + +} diff --git a/src/org/nutz/mvc/impl/reqmatcher/DefaultRequestMatcher.java b/src/org/nutz/mvc/impl/reqmatcher/DefaultRequestMatcher.java new file mode 100644 index 0000000000..848c22c573 --- /dev/null +++ b/src/org/nutz/mvc/impl/reqmatcher/DefaultRequestMatcher.java @@ -0,0 +1,68 @@ +package org.nutz.mvc.impl.reqmatcher; + +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +import org.nutz.lang.Strings; +import org.nutz.log.Log; +import org.nutz.log.Logs; +import org.nutz.mvc.ActionChain; +import org.nutz.mvc.ActionContext; +import org.nutz.mvc.ActionInfo; +import org.nutz.mvc.RequestMatcher; + +public class DefaultRequestMatcher implements RequestMatcher { + + private static final Log log = Logs.get(); + + protected Map chainMap; + + protected ActionChain defaultChain; + + public void add(String path, ActionInfo ai, ActionChain chain) { + if (ai.isForSpecialHttpMethod()) { + for (String httpMethod : ai.getHttpMethods()) { + if (chainMap == null) + chainMap = new HashMap(); + chainMap.put(httpMethod, chain); + } + } + else { + defaultChain = chain; + } + } + + public ActionChain match(ActionContext ac) { + String httpMethod = ""; + if (chainMap != null && !chainMap.isEmpty()) { + HttpServletRequest req = ac.getRequest(); + httpMethod = Strings.sNull(req.getMethod(), "GET").toUpperCase(); + ActionChain chain = chainMap.get(httpMethod); + // 找到了特殊HTTP方法的处理动作链 + if (null != chain) { + return chain; + } + } + // 这个 URL 所有的HTTP方法用统一的动作链处理 + if (null != defaultChain) { + return defaultChain; + } + if (chainMap != null && chainMap.size() != 0 && log.isDebugEnabled()) { + log.debugf("Path=[%s] available methods%s but request [%s], using the wrong http method?", ac.getPath(), chainMap.keySet(), httpMethod); + } + // 否则将认为不能处理 + return null; + } + + public void addChain(String httpMethod, ActionChain chain) { + if (chainMap == null) + chainMap = new HashMap(); + chainMap.put(httpMethod, chain); + } + + public void setDefaultChain(ActionChain defaultChain) { + this.defaultChain = defaultChain; + } +} diff --git a/src/org/nutz/mvc/upload/UploadAdaptor.java b/src/org/nutz/mvc/upload/UploadAdaptor.java index e5db44c5a0..970e8dc41b 100644 --- a/src/org/nutz/mvc/upload/UploadAdaptor.java +++ b/src/org/nutz/mvc/upload/UploadAdaptor.java @@ -65,7 +65,10 @@ public class UploadAdaptor extends PairAdaptor { private UploadingContext context; public UploadAdaptor() throws IOException { - context = new UploadingContext(File.createTempFile("nutz", null).getParent()); + File tmp = File.createTempFile("nutz", null); + String path = tmp.getParent(); + tmp.delete(); + context = new UploadingContext(path); } public UploadAdaptor(UploadingContext context) { diff --git a/src/org/nutz/mvc/upload/UploadingContext.java b/src/org/nutz/mvc/upload/UploadingContext.java index 0459cae905..9cbe6057ed 100644 --- a/src/org/nutz/mvc/upload/UploadingContext.java +++ b/src/org/nutz/mvc/upload/UploadingContext.java @@ -7,6 +7,7 @@ import org.nutz.filepool.SynchronizedFilePool; import org.nutz.lang.Encoding; import org.nutz.lang.Strings; +import org.nutz.lang.util.Regex; import org.nutz.log.Log; import org.nutz.log.Logs; @@ -73,6 +74,8 @@ public UploadingContext(FilePool pool) { * 一个正则表达式,描述了可以允许的文件内容类型 */ private String contentTypeFilter; + + private Pattern nameFilterPattern; public String getCharset() { return charset; @@ -131,6 +134,8 @@ public String getNameFilter() { public UploadingContext setNameFilter(String nameFilter) { this.nameFilter = nameFilter; + if (!Strings.isBlank(nameFilter)) + this.nameFilterPattern = Pattern.compile(nameFilter); return this; } @@ -138,7 +143,9 @@ public boolean isNameAccepted(String name) { if (null == nameFilter || Strings.isBlank(name) || "\"\"".equals(name)) //用户不选择文件时,文件名会是"" 两个双引号 return true; - return Pattern.matches(nameFilter, name.toLowerCase()); + if (nameFilterPattern == null) + return Regex.match(nameFilter, name.toLowerCase()); + return nameFilterPattern.matcher(name.toLowerCase()).find(); } public String getContentTypeFilter() { @@ -153,6 +160,6 @@ public UploadingContext setContentTypeFilter(String contentTypeFilter) { public boolean isContentTypeAccepted(String contentType) { if (null == contentTypeFilter || Strings.isBlank(contentType)) return true; - return Pattern.matches(contentTypeFilter, contentType.toLowerCase()); + return Regex.match(contentTypeFilter, contentType.toLowerCase()); } } diff --git a/src/org/nutz/mvc/upload/Uploads.java b/src/org/nutz/mvc/upload/Uploads.java index 6d923ff75c..8004e1cfdc 100644 --- a/src/org/nutz/mvc/upload/Uploads.java +++ b/src/org/nutz/mvc/upload/Uploads.java @@ -15,62 +15,72 @@ */ public abstract class Uploads { - /** - * @param req - * 请求对象 - * @return 当前会话的上传进度对象,如果没有上传,则返回 null - */ - public static UploadInfo getInfo(HttpServletRequest req) { - HttpSession session = Mvcs.getHttpSession(false); - if (session == null) - return null; - return (UploadInfo) session.getAttribute(UploadInfo.SESSION_NAME); - } + /** + * @param req + * 请求对象 + * @return 当前会话的上传进度对象,如果没有上传,则返回 null + */ + public static UploadInfo getInfo(HttpServletRequest req) { + try { + HttpSession session = Mvcs.getHttpSession(false); + if (session == null) + return null; + return (UploadInfo) session.getAttribute(UploadInfo.SESSION_NAME); + } catch (Throwable e) { + } + return null; + } - /** - * @param req - * 请求对象 - * @return 本次上传的进度对象 - */ - public static UploadInfo createInfo(HttpServletRequest req) { - UploadInfo info = new UploadInfo(); - HttpSession sess = Mvcs.getHttpSession(false); - if (null != sess) { - sess.setAttribute(UploadInfo.SESSION_NAME, info); - } - info.sum = req.getContentLength(); - return info; - } + /** + * @param req + * 请求对象 + * @return 本次上传的进度对象 + */ + public static UploadInfo createInfo(HttpServletRequest req) { + UploadInfo info = new UploadInfo(); + try { + HttpSession sess = Mvcs.getHttpSession(false); + if (null != sess) { + sess.setAttribute(UploadInfo.SESSION_NAME, info); + } + } catch (Throwable e) { + } + info.sum = req.getContentLength(); + return info; + } - /** - * 根据请求对象创建参数 MAP, 同时根据 QueryString,为 MAP 设置初始值 - * - * @param req - * 请求对象 - * @return 参数 MAP - */ - public static NutMap createParamsMap(HttpServletRequest req) { - NutMap params = new NutMap(); - // parse query strings - Enumeration en = req.getParameterNames(); - while (en.hasMoreElements()) { - String key = en.nextElement().toString(); - params.put(key, req.getParameter(key)); - } - return params; - } + /** + * 根据请求对象创建参数 MAP, 同时根据 QueryString,为 MAP 设置初始值 + * + * @param req + * 请求对象 + * @return 参数 MAP + */ + public static NutMap createParamsMap(HttpServletRequest req) { + NutMap params = new NutMap(); + // parse query strings + Enumeration en = req.getParameterNames(); + while (en.hasMoreElements()) { + String key = en.nextElement().toString(); + params.put(key, req.getParameter(key)); + } + return params; + } - /** - * 从当前会话中移除进度对象 - * - * @param req - * 请求对象 - */ - public static void removeInfo(HttpServletRequest req) { - HttpSession sess = req.getSession(false); - if (null != sess) { - sess.removeAttribute(UploadInfo.SESSION_NAME); - } - } + /** + * 从当前会话中移除进度对象 + * + * @param req + * 请求对象 + */ + public static void removeInfo(HttpServletRequest req) { + try { + HttpSession sess = req.getSession(false); + if (null != sess) { + sess.removeAttribute(UploadInfo.SESSION_NAME); + } + } catch (Throwable e) { + } + } } diff --git a/src/org/nutz/mvc/view/AbstractPathView.java b/src/org/nutz/mvc/view/AbstractPathView.java index 4f9261416b..fa1a4af017 100644 --- a/src/org/nutz/mvc/view/AbstractPathView.java +++ b/src/org/nutz/mvc/view/AbstractPathView.java @@ -1,30 +1,21 @@ package org.nutz.mvc.view; -import java.util.ArrayList; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSession; - import org.nutz.el.El; import org.nutz.lang.Lang; import org.nutz.lang.Strings; import org.nutz.lang.segment.CharSegment; import org.nutz.lang.segment.Segment; import org.nutz.lang.util.Context; -import org.nutz.mvc.ActionContext; -import org.nutz.mvc.Loading; -import org.nutz.mvc.Mvcs; -import org.nutz.mvc.View; -import org.nutz.mvc.config.AtMap; -import org.nutz.mvc.impl.processor.ViewProcessor; +import org.nutz.mvc.*; +import org.nutz.mvc.impl.contextCollector.*; + +import javax.servlet.http.HttpServletRequest; +import java.util.*; +import java.util.Map.Entry; /** - * @author mawm(ming300@gmail.com) - * @author wendal(wendal1985@gmail.com) + * @author mawm(ming300 @ gmail.com) + * @author wendal(wendal1985 @ gmail.com) */ public abstract class AbstractPathView implements View { @@ -32,6 +23,20 @@ public abstract class AbstractPathView implements View { private Map exps; + private static List defVcc = new ArrayList(); + + static { + defVcc.add(new SharedCollector()); + defVcc.add(new ServletContextCollector()); + defVcc.add(new AttrCollector()); + defVcc.add(new PathargsCollector()); + defVcc.add(new SessionCollector()); + defVcc.add(new ParamCollector()); + defVcc.add(new RefCollector()); + defVcc.add(new AtCollector()); + defVcc.add(new ReturnCollector()); + } + public AbstractPathView(String dest) { if (null != dest) { this.dest = new CharSegment(Strings.trim(dest)); @@ -51,9 +56,9 @@ protected String evalPath(HttpServletRequest req, Object obj) { // 解析每个表达式 if (exps.size() != 0) { - Context expContext = createContext(req, obj); - for (Entry en : exps.entrySet()) - context.set(en.getKey(), en.getValue().eval(expContext)); + Context expContext = createContext(req, obj); + for (Entry en : exps.entrySet()) + context.set(en.getKey(), en.getValue().eval(expContext)); } // 生成解析后的路径 return Strings.trim(this.dest.render(context).toString()); @@ -61,68 +66,17 @@ protected String evalPath(HttpServletRequest req, Object obj) { /** * 为一次 HTTP 请求,创建一个可以被表达式引擎接受的上下文对象 - * - * @param req - * HTTP 请求对象 - * @param obj - * 入口函数的返回值 + * + * @param req HTTP 请求对象 + * @param obj 入口函数的返回值 * @return 上下文对象 */ public static Context createContext(HttpServletRequest req, Object obj) { Context context = Lang.context(); - // 复制全局的上下文对象 - Object globalContext = Mvcs.getServletContext() - .getAttribute(Loading.CONTEXT_NAME); - if (globalContext != null) { - context.putAll((Context) globalContext); - } - - // 请求对象的属性列表 - Map req_attr = new HashMap(); - for (Enumeration en = req.getAttributeNames(); en.hasMoreElements();) { - String tem = en.nextElement(); - if (!tem.startsWith("$")) - req_attr.put(tem, req.getAttribute(tem)); - } - context.set("a", req_attr);// 兼容最初的写法 - context.set("req_attr", req_attr); - - ActionContext ac = Mvcs.getActionContext(); - if (ac != null) - context.set("pathargs", Mvcs.getActionContext().getPathArgs()); - - HttpSession session = Mvcs.getHttpSession(false); - if (session != null) { - Map session_attr = new HashMap(); - for (Enumeration en = session.getAttributeNames(); en.hasMoreElements();) { - String tem = en.nextElement(); - session_attr.put(tem, session.getAttribute(tem)); - } - context.set("session_attr", session_attr); + for (int i = 0; i < defVcc.size(); i++) { + ViewContextCollector vcc = defVcc.get(i); + context.putAll(vcc.collect(req, obj)); } - // 请求的参数表,需要兼容之前的p.参数, Fix issue 418 - Map p = new HashMap(); - for (Object o : Lang.enum2collection(req.getParameterNames(), new ArrayList())) { - String key = (String) o; - String value = req.getParameter(key); - p.put(key, value); - context.set(key, value);// 以支持直接获取请求参数 - } - context.set("p", p); - - Map u = new HashMap(); - AtMap at = Mvcs.getAtMap(); - if (at != null) { - for (Object o : at.keys()) { - String key = (String) o; - u.put(key, at.get(key)); - } - context.set("u", u); - } - - // 加入返回对象 - if (null != obj) - context.set(ViewProcessor.DEFAULT_ATTRIBUTE, obj); return context; } } diff --git a/src/org/nutz/mvc/view/HttpServerResponse.java b/src/org/nutz/mvc/view/HttpEnhanceResponse.java similarity index 69% rename from src/org/nutz/mvc/view/HttpServerResponse.java rename to src/org/nutz/mvc/view/HttpEnhanceResponse.java index 32b498a54d..dc05b656c9 100644 --- a/src/org/nutz/mvc/view/HttpServerResponse.java +++ b/src/org/nutz/mvc/view/HttpEnhanceResponse.java @@ -1,9 +1,9 @@ package org.nutz.mvc.view; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.OutputStream; import java.io.UnsupportedEncodingException; -import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -12,14 +12,16 @@ import org.nutz.castor.Castors; import org.nutz.http.Http; +import org.nutz.lang.Each; import org.nutz.lang.Encoding; import org.nutz.lang.Lang; import org.nutz.lang.Streams; import org.nutz.lang.Strings; +import org.nutz.lang.util.NutMap; import org.nutz.log.Log; import org.nutz.log.Logs; -public class HttpServerResponse implements Cloneable { +public class HttpEnhanceResponse implements Cloneable { private static final Log log = Logs.get(); @@ -27,19 +29,23 @@ public class HttpServerResponse implements Cloneable { private String statusText; - private Map header; + private NutMap header; private byte[] body; + + private boolean upperHeaderName; + + private String ifNoneMatch; - public HttpServerResponse() { - this.header = new HashMap(); + public HttpEnhanceResponse() { + this.header = new NutMap(); } - public HttpServerResponse clone() { - HttpServerResponse re = new HttpServerResponse(); + public HttpEnhanceResponse clone() { + HttpEnhanceResponse re = new HttpEnhanceResponse(); re.statusCode = statusCode; re.statusText = statusText; - re.header = new HashMap(); + re.header = new NutMap(); if (header != null) re.header.putAll(header); re.body = body; @@ -48,6 +54,10 @@ public HttpServerResponse clone() { private static final Pattern _P = Pattern.compile("^HTTP/1.\\d\\s+(\\d+)(\\s+(.*))?$"); + public NutMap header() { + return this.header; + } + public void updateBy(String str) { try { // 如果以 HTTP/1.x 开头,则认为是要输出 HTTP 头 @@ -73,7 +83,13 @@ public void updateBy(String str) { int p2 = line.indexOf(':'); String key = Strings.trim(line.substring(0, p2)); String val = Strings.trim(line.substring(p2 + 1)); - header.put(key, val); + if (!Strings.isBlank(key) && !Strings.isBlank(val)) { + if (upperHeaderName) { + key = key.toUpperCase(); + } + System.out.println("FF " + key + "=" + val); + header.addv(key, val); + } // 指向下一行 pos = end + 1; @@ -150,20 +166,44 @@ public void updateBody(String body) { } } - public void render(HttpServletResponse resp) { + public void render(final HttpServletResponse resp) { resp.setStatus(statusCode); // 标记是否需要sendError boolean flag = statusCode >= 400; if (null != header && header.size() > 0) { - for (Map.Entry en : header.entrySet()) { - resp.setHeader(en.getKey(), en.getValue()); + // 如果 Header 的值为数组,那么就设置成多个 + for (Map.Entry en : header.entrySet()) { + final String key = en.getKey(); + Object val = en.getValue(); + Lang.each(val, new Each() { + public void invoke(int index, Object ele, int length) { + if (null != ele) + resp.addHeader(key, ele.toString()); + } + }); } flag = false; } + + // 重定向链接不应该带body的, 3XX系列的响应都是这样 + if (statusCode > 300 && statusCode < 399) { + return; + } if (body != null) { + // 检查是否符合304 + String etag = Lang.sha1(new ByteArrayInputStream(body)); + if (!Strings.isBlank(ifNoneMatch)) { + if (etag.equalsIgnoreCase(ifNoneMatch)) { + statusCode = 304; + resp.setStatus(304); + return; + } + log.infof("ETag expect %s but %s", etag, ifNoneMatch); + } + resp.setHeader("ETag", etag); resp.setContentLength(body.length); OutputStream out; try { @@ -186,4 +226,11 @@ public void render(HttpServletResponse resp) { } } + public void setUpperHeaderName(boolean upperHeaderName) { + this.upperHeaderName = upperHeaderName; + } + + public void setIfNoneMatch(String ifNoneMatch) { + this.ifNoneMatch = ifNoneMatch; + } } \ No newline at end of file diff --git a/src/org/nutz/mvc/view/HttpStatusView.java b/src/org/nutz/mvc/view/HttpStatusView.java index 9a5096530c..5486a2ecde 100644 --- a/src/org/nutz/mvc/view/HttpStatusView.java +++ b/src/org/nutz/mvc/view/HttpStatusView.java @@ -58,14 +58,14 @@ public HttpStatusException(int status, String fmt, Object... args) { } - private HttpServerResponse info; + private HttpEnhanceResponse info; - public HttpStatusView(HttpServerResponse info) { + public HttpStatusView(HttpEnhanceResponse info) { this.info = info; } public HttpStatusView(int statusCode) { - info = new HttpServerResponse(); + info = new HttpEnhanceResponse(); info.updateCode(statusCode, null); } @@ -80,7 +80,7 @@ public HttpStatusView setBody(String body) { } public void render(HttpServletRequest req, HttpServletResponse resp, Object obj) { - HttpServerResponse info = this.info.clone(); + HttpEnhanceResponse info = this.info.clone(); if (null != obj) { // 指明了动态的 code diff --git a/src/org/nutz/repo/cache/simple/LRUCache.java b/src/org/nutz/repo/cache/simple/LRUCache.java index f1c4c3b5e1..635acad522 100644 --- a/src/org/nutz/repo/cache/simple/LRUCache.java +++ b/src/org/nutz/repo/cache/simple/LRUCache.java @@ -107,5 +107,9 @@ public synchronized Collection> getAll() { public synchronized List getValues() { return new ArrayList(map.values()); } + + public void setCacheSize(int cacheSize) { + this.cacheSize = cacheSize; + } } // end class LRUCache \ No newline at end of file diff --git a/src/org/nutz/repo/org/objectweb/asm/commons/GeneratorAdapter.java b/src/org/nutz/repo/org/objectweb/asm/commons/GeneratorAdapter.java index 86056e8383..4f9c81ea66 100644 --- a/src/org/nutz/repo/org/objectweb/asm/commons/GeneratorAdapter.java +++ b/src/org/nutz/repo/org/objectweb/asm/commons/GeneratorAdapter.java @@ -911,22 +911,23 @@ public void cast(final Type from, final Type to) { private static Type getBoxedType(final Type type) { switch (type.getSort()) { - case Type.BYTE: - return BYTE_TYPE; - case Type.BOOLEAN: - return BOOLEAN_TYPE; - case Type.SHORT: - return SHORT_TYPE; - case Type.CHAR: - return CHARACTER_TYPE; - case Type.INT: - return INTEGER_TYPE; - case Type.FLOAT: - return FLOAT_TYPE; - case Type.LONG: - return LONG_TYPE; - case Type.DOUBLE: - return DOUBLE_TYPE; + case Type.BYTE: + return BYTE_TYPE; + case Type.BOOLEAN: + return BOOLEAN_TYPE; + case Type.SHORT: + return SHORT_TYPE; + case Type.CHAR: + return CHARACTER_TYPE; + case Type.INT: + return INTEGER_TYPE; + case Type.FLOAT: + return FLOAT_TYPE; + case Type.LONG: + return LONG_TYPE; + case Type.DOUBLE: + return DOUBLE_TYPE; + default: } return type; } @@ -994,29 +995,30 @@ public void unbox(final Type type) { Type t = NUMBER_TYPE; Method sig = null; switch (type.getSort()) { - case Type.VOID: - return; - case Type.CHAR: - t = CHARACTER_TYPE; - sig = CHAR_VALUE; - break; - case Type.BOOLEAN: - t = BOOLEAN_TYPE; - sig = BOOLEAN_VALUE; - break; - case Type.DOUBLE: - sig = DOUBLE_VALUE; - break; - case Type.FLOAT: - sig = FLOAT_VALUE; - break; - case Type.LONG: - sig = LONG_VALUE; - break; - case Type.INT: - case Type.SHORT: - case Type.BYTE: - sig = INT_VALUE; + case Type.VOID: + return; + case Type.CHAR: + t = CHARACTER_TYPE; + sig = CHAR_VALUE; + break; + case Type.BOOLEAN: + t = BOOLEAN_TYPE; + sig = BOOLEAN_VALUE; + break; + case Type.DOUBLE: + sig = DOUBLE_VALUE; + break; + case Type.FLOAT: + sig = FLOAT_VALUE; + break; + case Type.LONG: + sig = LONG_VALUE; + break; + case Type.INT: + case Type.SHORT: + case Type.BYTE: + sig = INT_VALUE; + default: } if (sig == null) { checkCast(type); @@ -1074,53 +1076,54 @@ public Label mark() { */ public void ifCmp(final Type type, final int mode, final Label label) { switch (type.getSort()) { - case Type.LONG: - mv.visitInsn(Opcodes.LCMP); - break; - case Type.DOUBLE: - mv.visitInsn(mode == GE || mode == GT ? Opcodes.DCMPL - : Opcodes.DCMPG); - break; - case Type.FLOAT: - mv.visitInsn(mode == GE || mode == GT ? Opcodes.FCMPL - : Opcodes.FCMPG); - break; - case Type.ARRAY: - case Type.OBJECT: - switch (mode) { - case EQ: - mv.visitJumpInsn(Opcodes.IF_ACMPEQ, label); - return; - case NE: - mv.visitJumpInsn(Opcodes.IF_ACMPNE, label); - return; - } - throw new IllegalArgumentException("Bad comparison for type " - + type); - default: - int intOp = -1; - switch (mode) { - case EQ: - intOp = Opcodes.IF_ICMPEQ; - break; - case NE: - intOp = Opcodes.IF_ICMPNE; - break; - case GE: - intOp = Opcodes.IF_ICMPGE; - break; - case LT: - intOp = Opcodes.IF_ICMPLT; + case Type.LONG: + mv.visitInsn(Opcodes.LCMP); break; - case LE: - intOp = Opcodes.IF_ICMPLE; + case Type.DOUBLE: + mv.visitInsn(mode == GE || mode == GT ? Opcodes.DCMPL + : Opcodes.DCMPG); break; - case GT: - intOp = Opcodes.IF_ICMPGT; + case Type.FLOAT: + mv.visitInsn(mode == GE || mode == GT ? Opcodes.FCMPL + : Opcodes.FCMPG); break; - } - mv.visitJumpInsn(intOp, label); - return; + case Type.ARRAY: + case Type.OBJECT: + switch (mode) { + case EQ: + mv.visitJumpInsn(Opcodes.IF_ACMPEQ, label); + return; + case NE: + mv.visitJumpInsn(Opcodes.IF_ACMPNE, label); + return; + } + throw new IllegalArgumentException("Bad comparison for type " + + type); + default: + int intOp = -1; + switch (mode) { + case EQ: + intOp = Opcodes.IF_ICMPEQ; + break; + case NE: + intOp = Opcodes.IF_ICMPNE; + break; + case GE: + intOp = Opcodes.IF_ICMPGE; + break; + case LT: + intOp = Opcodes.IF_ICMPLT; + break; + case LE: + intOp = Opcodes.IF_ICMPLE; + break; + case GT: + intOp = Opcodes.IF_ICMPGT; + break; + default: + } + mv.visitJumpInsn(intOp, label); + return; } mv.visitJumpInsn(mode, label); } diff --git a/src/org/nutz/resource/Scans.java b/src/org/nutz/resource/Scans.java index 3c34125063..dd50e2f0a4 100644 --- a/src/org/nutz/resource/Scans.java +++ b/src/org/nutz/resource/Scans.java @@ -195,7 +195,7 @@ public List scan(String src) { */ public List scan(String src, String regex) { if (src.isEmpty()) - throw new RuntimeException("emtry src is NOT allow"); + throw new RuntimeException("empty src is NOT allow"); if ("/".equals(src)) throw new RuntimeException("root path is NOT allow"); List list = new ArrayList(); @@ -374,7 +374,7 @@ public static final Scans me() { /** * 将一组 NutResource 转换成 class 对象 * - * @param packagePath + * @param pkg * 包前缀 * @param list * 列表 diff --git a/src/org/nutz/resource/impl/JarResourceLocation.java b/src/org/nutz/resource/impl/JarResourceLocation.java index bc0414b679..05c47ea5f9 100644 --- a/src/org/nutz/resource/impl/JarResourceLocation.java +++ b/src/org/nutz/resource/impl/JarResourceLocation.java @@ -39,16 +39,18 @@ public void scan(String base, Pattern regex, List list) { name = name.substring(name.lastIndexOf('/') + 1); if (null == regex || regex.matcher(name).find()) { NutResource nutResource = new NutResource() { + String url = uriJarPrefix(uri, "!/" + ensName); + public InputStream getInputStream() throws IOException { - return new URL(uriJarPrefix(uri,"!/" + ensName)).openStream(); + return new URL(url).openStream(); } - + public int hashCode() { return (id() + ":" + ensName).hashCode(); } - + public String toString() { - return uriJarPrefix(uri, "!/" + ensName); + return url; } }; if (ensName.equals(base)) diff --git a/src/org/nutz/runner/NutRunner.java b/src/org/nutz/runner/NutRunner.java index 899d1f15a9..5d85c3a7d2 100644 --- a/src/org/nutz/runner/NutRunner.java +++ b/src/org/nutz/runner/NutRunner.java @@ -56,6 +56,8 @@ public abstract class NutRunner implements Runnable { * 睡眠于,如果本值不为 null,表示本线程正在睡眠,否则为运行中 */ protected Date downAt; + + protected boolean debug = true; /** * 新建一个启动器 @@ -85,6 +87,7 @@ public NutRunner setSleepAfterError(int sec) { /** * 主逻辑,用户代码不应该覆盖. */ + @Override public void run() { if (log == null) { log = Logs.get().setTag(rnm); @@ -151,17 +154,22 @@ protected void doIt() { // 修改一下本线程的时间 upAt = Times.now(); downAt = null; - log.debugf("%s [%d] : up", rnm, ++count); + if (debug && log.isDebugEnabled()) { + log.debugf("%s [%d] : up", rnm, ++count); + } // 执行业务 interval = exec(); - if (interval < 1) + if (interval < 1) { interval = 1; // 不能间隔0或者负数,会死线程的 + } // 等待一个周期 downAt = Times.now(); - log.debugf("%s [%d] : wait %ds(%dms)", rnm, count, interval / 1000, interval); + if (debug && log.isDebugEnabled()) { + log.debugf("%s [%d] : wait %ds(%dms)", rnm, count, interval / 1000, interval); + } lock.wait(interval); } catch (InterruptedException e) { @@ -185,6 +193,7 @@ protected void doIt() { /** * 返回格式为 [名称:总启动次数] 最后启动时间:最后休眠时间 - 休眠间隔 */ + @Override public String toString() { return String.format("[%s:%d] %s/%s - %d", rnm, @@ -286,7 +295,14 @@ public boolean isAlive() { */ @SuppressWarnings("deprecation") public void stop(Throwable err) { - myThread.stop(err); + myThread.stop(); } + public void setDebug(boolean debug) { + this.debug = debug; + } + + public boolean isDebug() { + return debug; + } } diff --git a/src/org/nutz/trans/Proton.java b/src/org/nutz/trans/Proton.java index 9d3c0e91c8..e1400324f6 100644 --- a/src/org/nutz/trans/Proton.java +++ b/src/org/nutz/trans/Proton.java @@ -29,6 +29,11 @@ public T get() { public void run() { obj = exec(); } + + public T invoke() { + this.run(); + return this.get(); + } /** * 需要子类实现的逻辑 diff --git a/src/org/nutz/validate/NutValidate.java b/src/org/nutz/validate/NutValidate.java new file mode 100644 index 0000000000..73efb90163 --- /dev/null +++ b/src/org/nutz/validate/NutValidate.java @@ -0,0 +1,269 @@ +package org.nutz.validate; + +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.nutz.lang.util.NutMap; +import org.nutz.validate.impl.*; + +/** + * 一个简便的验证工具类,接受一个 Map + * + *
    + * {
    +       // 针对字符串型的值,检查前是否要预先去掉左右空白
    +       trim : true,
    +       // 数字区间
    +       intRange : "(10,20]",
    +       // 日期范围的区间
    +       dateRange : "(2018-12-02,2018-12-31]",
    +       // 验证值的字符串形式,支持 "!" 开头
    +       regex : "^...$",
    +       // 枚举验证·整数
    +       intEnum : [1,3,4],
    +       intEnum : "1,3,4",
    +       // 枚举验证·字符串
    +       strEnum : ["A","B","C],
    +       strEnum : "A,B,C",
    +       // 通配符
    +       wildcard : "*A*",
    +       // 精确数字
    +       intValue : 3,
    +       intValue : "3",
    +       // 精确字符串
    +       strValue : "ABC",
    +       // 精确布尔
    +       boolValue : true,
    +       // 确保值非 null
    +       notNull : true,
    +       // 针对字符串的值,最大长度不超过多少
    +       maxLength : 23,
    +       // 针对字符串的值,最小长度不能低于多少
    +       minLength : 5,
    +   }
    + * 
    + * + *
      + *
    • 所有项目都是 `AND`的关系 + *
    • 检查的顺序会是 `trim > notNull > max/minLength > 其他` + *
    + * + * @author zozoh(zozohtnt@gmail.com) + */ +public class NutValidate { + + private List items; + + /** + * 构建一个空白的检查器 + */ + public NutValidate() { + items = new LinkedList(); + } + + /** + * @param map + * 复合条件 + */ + public NutValidate(Map map) { + this(); + items.clear(); + this.addAll(map); + this.ready(); + } + + /** + * 根据输入字符串自动判断类型 + * + * @param str + * 输入字符串 + */ + /** + * @param str + */ + public NutValidate(String str) { + this(); + + // 一定是非空的 + this.add(new NotNullValidator()); + // 通配符 + if (str.startsWith("*") || str.endsWith("*")) { + this.add(new WildcardValidator(str)); + } + // 正则表达式 + else if (str.startsWith("^") || str.startsWith("!^")) { + this.add(new RegexValidator(str)); + } + // 范围 + else if (str.matches("^[\\[(].+[\\])]$")) { + // 范围·日期 + if (str.indexOf('/') >= 0 || str.indexOf('-') >= 0) { + this.add(new DateRangeValidator(str)); + } + // 范围·数字 + else { + this.add(new IntRangeValidator(str)); + } + } + // 精确匹配数字 + else if (str.matches("^\\d+$")) { + this.add(new IntValueValidator(str)); + } + // 精确匹配字符串 + else { + this.add(new StrValueValidator(str)); + } + } + + /** + * 根据一个描述的表增加自身的检查项, + * + * @param map + * 描述检查项的 Map + * @return 自身以便链式赋值 + */ + public NutValidate addAll(Map map) { + NutMap m2 = NutMap.WRAP(map); + for (String key : m2.keySet()) { + // 针对字符串型的值,检查前是否要预先去掉左右空白 + if ("trim".equals(key)) { + this.items.add(new TrimValidator()); + } + // 数字区间 + else if ("intRange".equals(key)) { + String str = m2.getString(key); + this.items.add(new IntRangeValidator(str)); + } + // 日期范围的区间 + else if ("dateRange".equals(key)) { + String str = m2.getString(key); + this.items.add(new DateRangeValidator(str)); + } + // 验证值的字符串形式,支持 "!" 开头 + else if ("regex".equals(key)) { + String str = m2.getString(key); + this.items.add(new RegexValidator(str)); + } + // 枚举验证·整数 + else if ("intEnum".equals(key)) { + Object val = m2.get(key); + this.items.add(new IntEnumValidator(val)); + } + // 枚举验证·字符串 + else if ("strEnum".equals(key)) { + Object val = m2.get(key); + this.items.add(new StrEnumValidator(val)); + } + // 通配符 + else if ("wildcard".equals(key)) { + String str = m2.getString(key); + this.items.add(new WildcardValidator(str)); + } + // 精确数字 + else if ("intValue".equals(key)) { + Object val = m2.get(key); + this.items.add(new IntValueValidator(val)); + } + // 精确字符串 + else if ("strValue".equals(key)) { + Object val = m2.get(key); + this.items.add(new StrValueValidator(val)); + } + // 精确布尔 + else if ("boolValue".equals(key)) { + Object val = m2.get(key); + this.items.add(new BoolValueValidator(val)); + } + // 确保值非 null + else if ("notNull".equals(key)) { + this.items.add(new NotNullValidator()); + } + // 针对字符串的值,最大长度不超过多少 + else if ("maxLength".equals(key)) { + int len = m2.getInt(key); + this.items.add(new MaxLengthValidator(len)); + } + // 针对字符串的值,最小长度不能低于多少 + else if ("minLength".equals(key)) { + int len = m2.getInt(key); + this.items.add(new MinLengthValidator(len)); + } + // 其他的无视 + } + return this; + } + + /** + * 增加一个检查器 + * + * @param nvs + * 检查器列表 + * @return 自身以便链式赋值 + */ + public NutValidate add(NutValidator... nvs) { + for (NutValidator nv : nvs) + this.items.add(nv); + return this; + } + + /** + * 根据检查器的优先顺序,重新调整检查列表 + * + * @return 自身以便链式赋值 + */ + public NutValidate ready() { + Collections.sort(items, new Comparator() { + public int compare(NutValidator v1, NutValidator v2) { + return v1.order() - v2.order(); + } + }); + return this; + } + + /** + * 清除自身的检查链 + * + * @return 自身以便链式赋值 + */ + public NutValidate reset() { + items.clear(); + return this; + } + + /** + * 执行检查 + * + * @param val + * 被检查的值 + * @return 检查后的结果,可能会被修改,譬如 `trim` 操作 + * @throws NutValidateException + * - 如果任何一个检查器除了错误,就会抛出本错误,并中断后续的检查 + */ + public Object check(Object val) throws NutValidateException { + Object re = val; + for (NutValidator nv : items) { + re = nv.check(re); + } + return re; + } + + /** + * 看看某个给定的值是否能通过所有检查器的检查 + * + * @param val + * 被检查的值 + * @return true 通过了所有的检查。 false 某些检查未被通过 + */ + public boolean match(Object val) { + try { + this.check(val); + return true; + } + catch (NutValidateException e) { + return false; + } + } +} diff --git a/src/org/nutz/validate/NutValidateException.java b/src/org/nutz/validate/NutValidateException.java new file mode 100644 index 0000000000..75b168fb29 --- /dev/null +++ b/src/org/nutz/validate/NutValidateException.java @@ -0,0 +1,68 @@ +package org.nutz.validate; + +@SuppressWarnings("serial") +public class NutValidateException extends Exception { + + private Object expect; + + private Object value; + + private String reason; + + public NutValidateException(String reason) { + this.reason = reason; + } + + public NutValidateException(String reason, Object expect, Object value) { + this.reason = reason; + this.expect = expect; + this.value = value; + } + + public Object getExpect() { + return expect; + } + + public void setExpect(Object expect) { + this.expect = expect; + } + + public Object getValue() { + return value; + } + + public void setValue(Object value) { + this.value = value; + } + + public String getReason() { + return reason; + } + + public void setReason(String reason) { + this.reason = reason; + } + + @Override + public String getLocalizedMessage() { + return this.toString(); + } + + @Override + public String getMessage() { + return this.toString(); + } + + @Override + public String toString() { + String str = this.reason; + if (null != expect) { + str += ", expect [" + expect + "]"; + } + if (null != value) { + str += ", but [" + value + ']'; + } + return str; + } + +} diff --git a/src/org/nutz/validate/NutValidator.java b/src/org/nutz/validate/NutValidator.java new file mode 100644 index 0000000000..5ed5ec5d13 --- /dev/null +++ b/src/org/nutz/validate/NutValidator.java @@ -0,0 +1,29 @@ +package org.nutz.validate; + +public interface NutValidator { + + /** + * @param val + * 待检测的值 + * @return 修改过的值,如果没有修改则会返回原值 + * @throws NutValidateException + * 出错后用这个异常汇报具体错误细节 + */ + Object check(Object val) throws NutValidateException; + + /** + * 返回本检查器的优先级,越小越优先检查 + * + *
      + *
    • trim : 0 + *
    • notNull : 1 + *
    • minLength : 11 + *
    • maxLength : 12 + *
    • 其他 : > 100 + *
    + * + * @return 本检查器的优先级 + */ + int order(); + +} diff --git a/src/org/nutz/validate/impl/BoolValueValidator.java b/src/org/nutz/validate/impl/BoolValueValidator.java new file mode 100644 index 0000000000..d3bebcb4ec --- /dev/null +++ b/src/org/nutz/validate/impl/BoolValueValidator.java @@ -0,0 +1,26 @@ +package org.nutz.validate.impl; + +import org.nutz.castor.Castors; +import org.nutz.validate.NutValidateException; +import org.nutz.validate.NutValidator; + +public class BoolValueValidator implements NutValidator { + + private boolean B; + + public BoolValueValidator(Object any) { + this.B = Castors.me().castTo(any, Boolean.class); + } + + @Override + public Object check(Object val) throws NutValidateException { + boolean v = Castors.me().castTo(val, Boolean.class); + return this.B == v; + } + + @Override + public int order() { + return 1000; + } + +} diff --git a/src/org/nutz/validate/impl/DateRangeValidator.java b/src/org/nutz/validate/impl/DateRangeValidator.java new file mode 100644 index 0000000000..5a08bfffb8 --- /dev/null +++ b/src/org/nutz/validate/impl/DateRangeValidator.java @@ -0,0 +1,35 @@ +package org.nutz.validate.impl; + +import java.util.Date; + +import org.nutz.castor.Castors; +import org.nutz.lang.util.DateRegion; +import org.nutz.lang.util.Region; +import org.nutz.validate.NutValidateException; +import org.nutz.validate.NutValidator; + +public class DateRangeValidator implements NutValidator { + + private DateRegion range; + + public DateRangeValidator(String str) { + this.range = Region.Date(str); + } + + @Override + public Object check(Object val) throws NutValidateException { + if (null == val) + return null; + Date d = Castors.me().castTo(val, Date.class); + if (!range.match(d)) { + throw new NutValidateException("DateOutOfRange", range.toString(), d); + } + return val; + } + + @Override + public int order() { + return 102; + } + +} diff --git a/src/org/nutz/validate/impl/IntEnumValidator.java b/src/org/nutz/validate/impl/IntEnumValidator.java new file mode 100644 index 0000000000..b3819266a1 --- /dev/null +++ b/src/org/nutz/validate/impl/IntEnumValidator.java @@ -0,0 +1,57 @@ +package org.nutz.validate.impl; + +import java.util.ArrayList; +import java.util.List; + +import org.nutz.castor.Castors; +import org.nutz.lang.Each; +import org.nutz.lang.Lang; +import org.nutz.lang.Strings; +import org.nutz.validate.NutValidateException; +import org.nutz.validate.NutValidator; + +public class IntEnumValidator implements NutValidator { + + private int[] nbs; + + public IntEnumValidator(Object any) { + // 拆掉字符串 + if (any instanceof CharSequence) { + any = Strings.splitIgnoreBlank(any.toString()); + } + // 全都变成数字 + final List list = new ArrayList(); + Lang.each(any, new Each() { + public void invoke(int index, Object ele, int length) { + int n = Castors.me().castTo(ele, Integer.class); + list.add(n); + } + }); + // 整理成数组 + nbs = new int[list.size()]; + int i = 0; + for (int n : list) { + nbs[i++] = n; + } + } + + @Override + public Object check(Object val) throws NutValidateException { + if(null == val) { + return val; + } + int v = Castors.me().castTo(val, Integer.class); + for (int n : nbs) { + if (n == v) { + return v; + } + } + throw new NutValidateException("IntOutOfEnum", nbs.toString(), val); + } + + @Override + public int order() { + return 1000; + } + +} diff --git a/src/org/nutz/validate/impl/IntRangeValidator.java b/src/org/nutz/validate/impl/IntRangeValidator.java new file mode 100644 index 0000000000..86c7487b8f --- /dev/null +++ b/src/org/nutz/validate/impl/IntRangeValidator.java @@ -0,0 +1,33 @@ +package org.nutz.validate.impl; + +import org.nutz.castor.Castors; +import org.nutz.lang.util.IntRegion; +import org.nutz.lang.util.Region; +import org.nutz.validate.NutValidateException; +import org.nutz.validate.NutValidator; + +public class IntRangeValidator implements NutValidator { + + private IntRegion range; + + public IntRangeValidator(String str) { + this.range = Region.Int(str); + } + + @Override + public Object check(Object val) throws NutValidateException { + if(null==val) + return null; + int n = Castors.me().castTo(val, Integer.class); + if (!range.match(n)) { + throw new NutValidateException("IntOutOfRange", range.toString(), n); + } + return val; + } + + @Override + public int order() { + return 101; + } + +} diff --git a/src/org/nutz/validate/impl/IntValueValidator.java b/src/org/nutz/validate/impl/IntValueValidator.java new file mode 100644 index 0000000000..8a9fc47226 --- /dev/null +++ b/src/org/nutz/validate/impl/IntValueValidator.java @@ -0,0 +1,29 @@ +package org.nutz.validate.impl; + +import org.nutz.castor.Castors; +import org.nutz.validate.NutValidateException; +import org.nutz.validate.NutValidator; + +public class IntValueValidator implements NutValidator { + + private int N; + + public IntValueValidator(Object any) { + this.N = Castors.me().castTo(any, Integer.class); + } + + @Override + public Object check(Object val) throws NutValidateException { + if (null == val) { + return null; + } + int v = Castors.me().castTo(val, Integer.class); + return this.N == v; + } + + @Override + public int order() { + return 1000; + } + +} diff --git a/src/org/nutz/validate/impl/MaxLengthValidator.java b/src/org/nutz/validate/impl/MaxLengthValidator.java new file mode 100644 index 0000000000..107199f9e8 --- /dev/null +++ b/src/org/nutz/validate/impl/MaxLengthValidator.java @@ -0,0 +1,30 @@ +package org.nutz.validate.impl; + +import org.nutz.validate.NutValidateException; +import org.nutz.validate.NutValidator; + +public class MaxLengthValidator implements NutValidator { + + private int len; + + public MaxLengthValidator(int len) { + this.len = len; + } + + @Override + public Object check(Object val) throws NutValidateException { + if(null==val) + return null; + String s = val.toString(); + if(s.length() > len) { + throw new NutValidateException("StrTooLong", len, s); + } + return val; + } + + @Override + public int order() { + return 12; + } + +} diff --git a/src/org/nutz/validate/impl/MinLengthValidator.java b/src/org/nutz/validate/impl/MinLengthValidator.java new file mode 100644 index 0000000000..232037da8b --- /dev/null +++ b/src/org/nutz/validate/impl/MinLengthValidator.java @@ -0,0 +1,30 @@ +package org.nutz.validate.impl; + +import org.nutz.validate.NutValidateException; +import org.nutz.validate.NutValidator; + +public class MinLengthValidator implements NutValidator { + + private int len; + + public MinLengthValidator(int len) { + this.len = len; + } + + @Override + public Object check(Object val) throws NutValidateException { + if(null==val) + return null; + String s = val.toString(); + if(s.length() < len) { + throw new NutValidateException("StrTooShort", len, s); + } + return val; + } + + @Override + public int order() { + return 11; + } + +} diff --git a/src/org/nutz/validate/impl/NotNullValidator.java b/src/org/nutz/validate/impl/NotNullValidator.java new file mode 100644 index 0000000000..964aa97b87 --- /dev/null +++ b/src/org/nutz/validate/impl/NotNullValidator.java @@ -0,0 +1,21 @@ +package org.nutz.validate.impl; + +import org.nutz.validate.NutValidateException; +import org.nutz.validate.NutValidator; + +public class NotNullValidator implements NutValidator { + + @Override + public Object check(Object val) throws NutValidateException { + if(null== val) { + throw new NutValidateException("IsNull"); + } + return val; + } + + @Override + public int order() { + return 0; + } + +} diff --git a/src/org/nutz/validate/impl/RegexValidator.java b/src/org/nutz/validate/impl/RegexValidator.java new file mode 100644 index 0000000000..da1d5a0f84 --- /dev/null +++ b/src/org/nutz/validate/impl/RegexValidator.java @@ -0,0 +1,41 @@ +package org.nutz.validate.impl; + +import java.util.regex.Pattern; + +import org.nutz.lang.Strings; +import org.nutz.lang.util.Regex; +import org.nutz.validate.NutValidateException; +import org.nutz.validate.NutValidator; + +public class RegexValidator implements NutValidator { + + private boolean not; + + private Pattern p; + + public RegexValidator(String regex) { + if(regex.startsWith("!")) { + this.not = true; + regex = Strings.trim(regex.substring(1)); + } + this.p = Regex.getPattern(regex); + } + + @Override + public Object check(Object val) throws NutValidateException { + if(null==val) + return null; + String s = val.toString(); + boolean isMatched = p.matcher(s).find(); + if (isMatched ^ !not) { + throw new NutValidateException("InvalidString", p.toString(), s); + } + return val; + } + + @Override + public int order() { + return 1000; + } + +} diff --git a/src/org/nutz/validate/impl/StrEnumValidator.java b/src/org/nutz/validate/impl/StrEnumValidator.java new file mode 100644 index 0000000000..01ac59a1f6 --- /dev/null +++ b/src/org/nutz/validate/impl/StrEnumValidator.java @@ -0,0 +1,54 @@ +package org.nutz.validate.impl; + +import java.util.ArrayList; +import java.util.List; + +import org.nutz.lang.Each; +import org.nutz.lang.Lang; +import org.nutz.lang.Strings; +import org.nutz.validate.NutValidateException; +import org.nutz.validate.NutValidator; + +public class StrEnumValidator implements NutValidator { + + private String[] strs; + + public StrEnumValidator(Object any) { + // 拆掉字符串 + if (any instanceof CharSequence) { + strs = Strings.splitIgnoreBlank(any.toString()); + } + // 其他的循环一下 + else { + // 全都变成字符串 + final List list = new ArrayList(); + Lang.each(any, new Each() { + public void invoke(int index, Object ele, int length) { + list.add(ele.toString()); + } + }); + // 整理成数组 + strs = list.toArray(new String[list.size()]); + } + } + + @Override + public Object check(Object val) throws NutValidateException { + if (null == val) { + return val; + } + String s = val.toString(); + for (String str : strs) { + if (str.equals(s)) { + return s; + } + } + throw new NutValidateException("StrOutOfEnum", strs.toString(), val); + } + + @Override + public int order() { + return 1000; + } + +} diff --git a/src/org/nutz/validate/impl/StrValueValidator.java b/src/org/nutz/validate/impl/StrValueValidator.java new file mode 100644 index 0000000000..0693ae4f91 --- /dev/null +++ b/src/org/nutz/validate/impl/StrValueValidator.java @@ -0,0 +1,27 @@ +package org.nutz.validate.impl; + +import org.nutz.validate.NutValidateException; +import org.nutz.validate.NutValidator; + +public class StrValueValidator implements NutValidator { + + private String str; + + public StrValueValidator(Object any){ + this.str = any.toString(); + } + + @Override + public Object check(Object val) throws NutValidateException { + if(null==val) { + return val; + } + return str.equals(val); + } + + @Override + public int order() { + return 1000; + } + +} diff --git a/src/org/nutz/validate/impl/TrimValidator.java b/src/org/nutz/validate/impl/TrimValidator.java new file mode 100644 index 0000000000..1259102527 --- /dev/null +++ b/src/org/nutz/validate/impl/TrimValidator.java @@ -0,0 +1,21 @@ +package org.nutz.validate.impl; + +import org.nutz.lang.Strings; +import org.nutz.validate.NutValidateException; +import org.nutz.validate.NutValidator; + +public class TrimValidator implements NutValidator { + + @Override + public Object check(Object val) throws NutValidateException { + if (null == val) + return null; + return Strings.trim(val.toString()); + } + + @Override + public int order() { + return 0; + } + +} diff --git a/src/org/nutz/validate/impl/WildcardValidator.java b/src/org/nutz/validate/impl/WildcardValidator.java new file mode 100644 index 0000000000..c750014a22 --- /dev/null +++ b/src/org/nutz/validate/impl/WildcardValidator.java @@ -0,0 +1,73 @@ +package org.nutz.validate.impl; + +import org.nutz.validate.NutValidateException; +import org.nutz.validate.NutValidator; + +public class WildcardValidator implements NutValidator { + + /** + * 查找的方式: + *
      + *
    • 0: 包含 + *
    • 1: 以给定字符开头 + *
    • -1: 以给定字符结尾 + *
    + */ + private int mode; + + private String str; + + private String primary; + + public WildcardValidator(String s) { + this.primary = s; + boolean wBegin = s.startsWith("*"); + boolean wEnd = s.endsWith("*"); + // 开头匹配模式,或者包含 + if (wBegin) { + this.mode = wEnd ? 0 : 1; + } + // 结尾匹配模式 + else if (wEnd) { + this.mode = -1; + } + // 默认包含 + else { + this.mode = 0; + } + } + + @Override + public Object check(Object val) throws NutValidateException { + if (null == val) { + return val; + } + String v = val.toString(); + // 开头匹配模式 + if (this.mode == 1) { + if (v.startsWith(str)) { + return v; + } + } + // 结尾匹配模式 + else if (this.mode == -1) { + if (v.endsWith(str)) { + return v; + } + } + // 包含 + else { + if (v.indexOf(str) >= 0) { + return v; + } + } + // 报错 + throw new NutValidateException("WildcardUnmatched", primary, val); + } + + @Override + public int order() { + return 0; + } + +} diff --git a/test/config/prefix.properties b/test/config/prefix.properties new file mode 100644 index 0000000000..19c75019bb --- /dev/null +++ b/test/config/prefix.properties @@ -0,0 +1,2 @@ +test.p1 =p1 +test.p2 =p2 \ No newline at end of file diff --git a/test/data/menu.json b/test/data/menu.json new file mode 100644 index 0000000000..1bdb8517c1 --- /dev/null +++ b/test/data/menu.json @@ -0,0 +1,1957 @@ +[ + { + "id": "1", + "menuName": "系统管理", + "parentId": "0", + "orderNum": "1", + "url": "#", + "menuType": "M", + "visible": false, + "perms": "", + "icon": "fa fa-gear", + "remark": "系统管理目录", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00", + "children": [ + { + "id": "100", + "menuName": "用户管理", + "parentId": "1", + "orderNum": "1", + "url": "/sys/user", + "menuType": "C", + "visible": false, + "perms": "sys:user:view", + "icon": "#", + "children": [ + { + "id": "1000", + "menuName": "用户查询", + "parentId": "100", + "orderNum": "1", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:user:list", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + }, + { + "id": "1001", + "menuName": "用户新增", + "parentId": "100", + "orderNum": "2", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:user:add", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + }, + { + "id": "1002", + "menuName": "用户修改", + "parentId": "100", + "orderNum": "3", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:user:edit", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + }, + { + "id": "1003", + "menuName": "用户删除", + "parentId": "100", + "orderNum": "4", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:user:remove", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + }, + { + "id": "1004", + "menuName": "用户导出", + "parentId": "100", + "orderNum": "5", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:user:export", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + }, + { + "id": "1005", + "menuName": "用户导入", + "parentId": "100", + "orderNum": "6", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:user:import", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + }, + { + "id": "1006", + "menuName": "重置密码", + "parentId": "100", + "orderNum": "7", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:user:resetPwd", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + } + ] + }, + { + "id": "101", + "menuName": "角色管理", + "parentId": "1", + "orderNum": "2", + "url": "/sys/role", + "menuType": "C", + "visible": false, + "perms": "sys:role:view", + "icon": "#", + "children": [ + { + "id": "1007", + "menuName": "角色查询", + "parentId": "101", + "orderNum": "1", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:role:list", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + }, + { + "id": "1008", + "menuName": "角色新增", + "parentId": "101", + "orderNum": "2", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:role:add", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + }, + { + "id": "1009", + "menuName": "角色修改", + "parentId": "101", + "orderNum": "3", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:role:edit", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + }, + { + "id": "1010", + "menuName": "角色删除", + "parentId": "101", + "orderNum": "4", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:role:remove", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + }, + { + "id": "1011", + "menuName": "角色导出", + "parentId": "101", + "orderNum": "5", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:role:export", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + } + ] + }, + { + "id": "102", + "menuName": "菜单管理", + "parentId": "1", + "orderNum": "3", + "url": "/sys/menu", + "menuType": "C", + "visible": false, + "perms": "sys:menu:view", + "icon": "#", + "children": [ + { + "id": "1012", + "menuName": "菜单查询", + "parentId": "102", + "orderNum": "1", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:menu:list", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + }, + { + "id": "1013", + "menuName": "菜单新增", + "parentId": "102", + "orderNum": "2", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:menu:add", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + }, + { + "id": "1014", + "menuName": "菜单修改", + "parentId": "102", + "orderNum": "3", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:menu:edit", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + }, + { + "id": "1015", + "menuName": "菜单删除", + "parentId": "102", + "orderNum": "4", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:menu:remove", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + } + ] + }, + { + "id": "103", + "menuName": "部门管理", + "parentId": "1", + "orderNum": "4", + "url": "/sys/dept", + "menuType": "C", + "visible": false, + "perms": "sys:dept:view", + "icon": "#", + "children": [ + { + "id": "1016", + "menuName": "部门查询", + "parentId": "103", + "orderNum": "1", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:dept:list", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + }, + { + "id": "1017", + "menuName": "部门新增", + "parentId": "103", + "orderNum": "2", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:dept:add", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + }, + { + "id": "1018", + "menuName": "部门修改", + "parentId": "103", + "orderNum": "3", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:dept:edit", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + }, + { + "id": "1019", + "menuName": "部门删除", + "parentId": "103", + "orderNum": "4", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:dept:remove", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + } + ] + }, + { + "id": "104", + "menuName": "岗位管理", + "parentId": "1", + "orderNum": "5", + "url": "/sys/post", + "menuType": "C", + "visible": false, + "perms": "sys:post:view", + "icon": "#", + "children": [ + { + "id": "1020", + "menuName": "岗位查询", + "parentId": "104", + "orderNum": "1", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:post:list", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + }, + { + "id": "1021", + "menuName": "岗位新增", + "parentId": "104", + "orderNum": "2", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:post:add", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + }, + { + "id": "1022", + "menuName": "岗位修改", + "parentId": "104", + "orderNum": "3", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:post:edit", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + }, + { + "id": "1023", + "menuName": "岗位删除", + "parentId": "104", + "orderNum": "4", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:post:remove", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + }, + { + "id": "1024", + "menuName": "岗位导出", + "parentId": "104", + "orderNum": "5", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:post:export", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + } + ] + }, + { + "id": "105", + "menuName": "字典管理", + "parentId": "1", + "orderNum": "6", + "url": "/sys/dict", + "menuType": "C", + "visible": false, + "perms": "sys:dict:view", + "icon": "#", + "remark": "字典管理菜单", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00", + "children": [ + { + "id": "1025", + "menuName": "字典查询", + "parentId": "105", + "orderNum": "1", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:dict:list", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + }, + { + "id": "1026", + "menuName": "字典新增", + "parentId": "105", + "orderNum": "2", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:dict:add", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + }, + { + "id": "1027", + "menuName": "字典修改", + "parentId": "105", + "orderNum": "3", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:dict:edit", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + }, + { + "id": "1028", + "menuName": "字典删除", + "parentId": "105", + "orderNum": "4", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:dict:remove", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + }, + { + "id": "1029", + "menuName": "字典导出", + "parentId": "105", + "orderNum": "5", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:dict:export", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + } + ] + }, + { + "id": "106", + "menuName": "参数设置", + "parentId": "1", + "orderNum": "7", + "url": "/sys/config", + "menuType": "C", + "visible": false, + "perms": "sys:config:view", + "icon": "#", + "remark": "参数设置菜单", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00", + "children": [ + { + "id": "1030", + "menuName": "参数查询", + "parentId": "106", + "orderNum": "1", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:config:list", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + }, + { + "id": "1031", + "menuName": "参数新增", + "parentId": "106", + "orderNum": "2", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:config:add", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + }, + { + "id": "1032", + "menuName": "参数修改", + "parentId": "106", + "orderNum": "3", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:config:edit", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + }, + { + "id": "1033", + "menuName": "参数删除", + "parentId": "106", + "orderNum": "4", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:config:remove", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + }, + { + "id": "1034", + "menuName": "参数导出", + "parentId": "106", + "orderNum": "5", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:config:export", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + } + ] + }, + { + "id": "1062", + "menuName": "区域", + "parentId": "1", + "orderNum": "9", + "url": "/sys/area", + "menuType": "C", + "visible": false, + "perms": "sys:area:view", + "icon": "#", + "children": [ + { + "id": "1058", + "menuName": "区域查询", + "parentId": "1062", + "orderNum": "1", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:area:list", + "icon": "#" + }, + { + "id": "1059", + "menuName": "区域新增", + "parentId": "1062", + "orderNum": "2", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:area:add", + "icon": "#" + }, + { + "id": "1060", + "menuName": "区域修改", + "parentId": "1062", + "orderNum": "3", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:area:edit", + "icon": "#" + }, + { + "id": "1061", + "menuName": "区域删除", + "parentId": "1062", + "orderNum": "4", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:area:remove", + "icon": "#" + } + ] + }, + { + "id": "107", + "menuName": "通知公告", + "parentId": "1", + "orderNum": "8", + "url": "/sys/notice", + "menuType": "C", + "visible": true, + "perms": "sys:notice:view", + "icon": "#", + "children": [ + { + "id": "1035", + "menuName": "公告查询", + "parentId": "107", + "orderNum": "1", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:notice:list", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + }, + { + "id": "1036", + "menuName": "公告新增", + "parentId": "107", + "orderNum": "2", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:notice:add", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + }, + { + "id": "1037", + "menuName": "公告修改", + "parentId": "107", + "orderNum": "3", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:notice:edit", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + }, + { + "id": "1038", + "menuName": "公告删除", + "parentId": "107", + "orderNum": "4", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:notice:remove", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + } + ] + } + ] + }, + { + "id": "2", + "menuName": "系统监控", + "parentId": "0", + "orderNum": "20", + "url": "#", + "menuType": "M", + "visible": false, + "perms": "", + "icon": "fa fa-video-camera", + "children": [ + { + "id": "108", + "menuName": "日志管理", + "parentId": "2", + "orderNum": "9", + "url": "#", + "menuType": "M", + "visible": false, + "perms": "", + "icon": "#", + "updateBy": "1", + "updateTime": "2019-05-13 11:41:03", + "children": [ + { + "id": "500", + "menuName": "操作日志", + "parentId": "108", + "orderNum": "1", + "url": "/monitor/operlog", + "menuType": "C", + "visible": false, + "perms": "monitor:operlog:view", + "icon": "#", + "remark": "操作日志菜单", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00", + "children": [ + { + "id": "1039", + "menuName": "操作查询", + "parentId": "500", + "orderNum": "1", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "monitor:operlog:list", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + }, + { + "id": "1040", + "menuName": "操作删除", + "parentId": "500", + "orderNum": "2", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "monitor:operlog:remove", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + }, + { + "id": "1041", + "menuName": "详细信息", + "parentId": "500", + "orderNum": "3", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "monitor:operlog:detail", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + }, + { + "id": "1042", + "menuName": "日志导出", + "parentId": "500", + "orderNum": "4", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "monitor:operlog:export", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + } + ] + }, + { + "id": "501", + "menuName": "登录日志", + "parentId": "108", + "orderNum": "2", + "url": "/monitor/logininfor", + "menuType": "C", + "visible": false, + "perms": "monitor:logininfor:view", + "icon": "#", + "remark": "登录日志菜单", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00", + "children": [ + { + "id": "1043", + "menuName": "登录查询", + "parentId": "501", + "orderNum": "1", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "monitor:logininfor:list", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + }, + { + "id": "1044", + "menuName": "登录删除", + "parentId": "501", + "orderNum": "2", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "monitor:logininfor:remove", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + }, + { + "id": "1045", + "menuName": "日志导出", + "parentId": "501", + "orderNum": "3", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "monitor:logininfor:export", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + } + ] + } + ] + }, + { + "id": "109", + "menuName": "在线用户", + "parentId": "2", + "orderNum": "1", + "url": "/monitor/online", + "menuType": "C", + "visible": true, + "perms": "monitor:online:view", + "icon": "#", + "updateBy": "1", + "updateTime": "2019-05-13 11:21:58", + "children": [ + { + "id": "1046", + "menuName": "在线查询", + "parentId": "109", + "orderNum": "1", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "monitor:online:list", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + }, + { + "id": "1047", + "menuName": "批量强退", + "parentId": "109", + "orderNum": "2", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "monitor:online:batchForceLogout", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + }, + { + "id": "1048", + "menuName": "单条强退", + "parentId": "109", + "orderNum": "3", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "monitor:online:forceLogout", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + } + ] + }, + { + "id": "110", + "menuName": "定时任务", + "parentId": "2", + "orderNum": "20", + "url": "/sys/task", + "menuType": "C", + "visible": false, + "perms": "sys:task:view", + "icon": "#", + "updateBy": "1", + "updateTime": "2019-05-10 15:26:03", + "children": [ + { + "id": "1049", + "menuName": "任务查询", + "parentId": "110", + "orderNum": "1", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:task:list", + "icon": "#" + }, + { + "id": "1050", + "menuName": "任务新增", + "parentId": "110", + "orderNum": "2", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:task:add", + "icon": "#" + }, + { + "id": "1051", + "menuName": "任务修改", + "parentId": "110", + "orderNum": "3", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:task:edit", + "icon": "#" + }, + { + "id": "1052", + "menuName": "任务删除", + "parentId": "110", + "orderNum": "4", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "sys:task:remove", + "icon": "#" + }, + { + "id": "1053", + "menuName": "状态修改", + "parentId": "110", + "orderNum": "5", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "monitor:job:changeStatus", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + }, + { + "id": "1054", + "menuName": "任务详细", + "parentId": "110", + "orderNum": "6", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "monitor:job:detail", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + }, + { + "id": "1055", + "menuName": "任务导出", + "parentId": "110", + "orderNum": "7", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "monitor:job:export", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + } + ] + }, + { + "id": "111", + "menuName": "数据监控", + "parentId": "2", + "orderNum": "3", + "url": "/druid", + "menuType": "C", + "visible": false, + "perms": "", + "icon": "#" + }, + { + "id": "112", + "menuName": "服务监控", + "parentId": "2", + "orderNum": "3", + "url": "/monitor/server", + "menuType": "C", + "visible": false, + "perms": "monitor:server:view", + "icon": "#", + "remark": "服务监控菜单", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + } + ] + }, + { + "id": "3", + "menuName": "系统工具", + "parentId": "0", + "orderNum": "30", + "url": "#", + "menuType": "M", + "visible": false, + "perms": "", + "icon": "fa fa-bars", + "children": [ + { + "id": "113", + "menuName": "表单构建", + "parentId": "3", + "orderNum": "1", + "url": "/tool/build", + "menuType": "C", + "visible": false, + "perms": "tool:build:view", + "icon": "#", + "remark": "表单构建菜单", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + }, + { + "id": "114", + "menuName": "代码生成", + "parentId": "3", + "orderNum": "2", + "url": "/tool/gen", + "menuType": "C", + "visible": false, + "perms": "tool:gen:view", + "icon": "#", + "remark": "代码生成菜单", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00", + "children": [ + { + "id": "1056", + "menuName": "生成查询", + "parentId": "114", + "orderNum": "1", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "tool:gen:list", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + }, + { + "id": "1057", + "menuName": "生成代码", + "parentId": "114", + "orderNum": "2", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "tool:gen:code", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + }, + { + "id": "f0f04abcf24c4dd7ac641e02b799ed26", + "menuName": "代码预览", + "parentId": "114", + "orderNum": "30", + "url": "", + "menuType": "F", + "visible": false, + "perms": "tool:gen:preview", + "icon": "", + "createBy": "1", + "createTime": "2020-02-20 11:57:48", + "updateBy": "1", + "updateTime": "2020-02-20 11:57:48" + } + ] + }, + { + "id": "115", + "menuName": "系统接口", + "parentId": "3", + "orderNum": "3", + "url": "/tool/swagger", + "menuType": "C", + "visible": false, + "perms": "tool:swagger:view", + "icon": "#", + "remark": "系统接口菜单", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2018-03-16 11:33:00" + } + ] + }, + { + "id": "45844c9885a04c85b3d8d12837f78648", + "menuName": "DEMO", + "parentId": "0", + "orderNum": "80", + "url": "", + "menuType": "M", + "visible": false, + "perms": "", + "icon": "fa fa-coffee", + "createBy": "1", + "createTime": "2020-03-30 17:25:34", + "updateBy": "1", + "updateTime": "2020-03-30 17:25:34", + "children": [ + { + "id": "0qr0v7vugsjthp0n1pakqq675c", + "menuName": "宠物", + "parentId": "45844c9885a04c85b3d8d12837f78648", + "orderNum": "30", + "url": "/test/pet", + "menuType": "C", + "visible": false, + "perms": "test:pet:view", + "icon": "#", + "updateBy": "1", + "updateTime": "2020-03-30 17:26:25", + "children": [ + { + "id": "1k3s40clkah99phi7lnpnr7rdr", + "menuName": "宠物删除", + "parentId": "0qr0v7vugsjthp0n1pakqq675c", + "orderNum": "4", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "test:pet:remove", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-01 00:00:00", + "updateBy": "haiming", + "updateTime": "2018-03-01 00:00:00" + }, + { + "id": "42pvr8o88oj8qpib71krf12g43", + "menuName": "宠物查询", + "parentId": "0qr0v7vugsjthp0n1pakqq675c", + "orderNum": "1", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "test:pet:list", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-01 00:00:00", + "updateBy": "haiming", + "updateTime": "2018-03-01 00:00:00" + }, + { + "id": "68okl0igqkjidoi7gs3fi6qm8q", + "menuName": "宠物新增", + "parentId": "0qr0v7vugsjthp0n1pakqq675c", + "orderNum": "2", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "test:pet:add", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-01 00:00:00", + "updateBy": "haiming", + "updateTime": "2018-03-01 00:00:00" + }, + { + "id": "7hhabctmo6jdiqccdq49tqtskd", + "menuName": "宠物修改", + "parentId": "0qr0v7vugsjthp0n1pakqq675c", + "orderNum": "3", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "test:pet:edit", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-01 00:00:00", + "updateBy": "haiming", + "updateTime": "2018-03-01 00:00:00" + } + ] + }, + { + "id": "4kvp92hg5mj30r94i9teh6ue5r", + "menuName": "主人", + "parentId": "45844c9885a04c85b3d8d12837f78648", + "orderNum": "20", + "url": "/test/master", + "menuType": "C", + "visible": false, + "perms": "test:master:view", + "icon": "#", + "updateBy": "1", + "updateTime": "2020-03-30 17:26:11", + "children": [ + { + "id": "5vrtmm99m0h01qfsoa9m1daiks", + "menuName": "主人修改", + "parentId": "4kvp92hg5mj30r94i9teh6ue5r", + "orderNum": "3", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "test:master:edit", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-01 00:00:00", + "updateBy": "haiming", + "updateTime": "2018-03-01 00:00:00" + }, + { + "id": "6turp2utoqhsjrt8cuiv4d5gsi", + "menuName": "主人删除", + "parentId": "4kvp92hg5mj30r94i9teh6ue5r", + "orderNum": "4", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "test:master:remove", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-01 00:00:00", + "updateBy": "haiming", + "updateTime": "2018-03-01 00:00:00" + }, + { + "id": "r3n9o4g8m6hm5qlqkirj393s9j", + "menuName": "主人新增", + "parentId": "4kvp92hg5mj30r94i9teh6ue5r", + "orderNum": "2", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "test:master:add", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-01 00:00:00", + "updateBy": "haiming", + "updateTime": "2018-03-01 00:00:00" + }, + { + "id": "s1u0dfo164jjnp601okajsd9pn", + "menuName": "主人查询", + "parentId": "4kvp92hg5mj30r94i9teh6ue5r", + "orderNum": "1", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "test:master:list", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-01 00:00:00", + "updateBy": "haiming", + "updateTime": "2018-03-01 00:00:00" + } + ] + } + ] + }, + { + "id": "7bd0844c03214140a403dda2989d54f7", + "menuName": "微信", + "parentId": "0", + "orderNum": "50", + "url": "", + "menuType": "M", + "visible": false, + "perms": "", + "icon": "fa fa-comments", + "updateBy": "1", + "updateTime": "2019-12-05 17:25:08", + "children": [ + { + "id": "7d14fm1jkgijtovnkge18or2r6", + "menuName": "微信菜单", + "parentId": "7bd0844c03214140a403dda2989d54f7", + "orderNum": "1", + "url": "/wx/menu", + "menuType": "C", + "visible": false, + "perms": "wx:menu:view", + "icon": "#", + "updateBy": "1", + "updateTime": "2019-05-10 17:22:07", + "children": [ + { + "id": "0a87d161daac4a9888f8099fd76f1b3f", + "menuName": "推送菜单", + "parentId": "7d14fm1jkgijtovnkge18or2r6", + "orderNum": "5", + "url": "", + "menuType": "F", + "visible": false, + "perms": "wx:weixinMenu:push", + "icon": "", + "createBy": "1", + "createTime": "2019-05-10 15:40:05", + "updateBy": "1", + "updateTime": "2019-05-10 15:40:05" + }, + { + "id": "12n0a34518g8urdjo6kp83h5uj", + "menuName": "微信菜单修改", + "parentId": "7d14fm1jkgijtovnkge18or2r6", + "orderNum": "3", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "wx:menu:edit", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-01 00:00:00", + "updateBy": "haiming", + "updateTime": "2018-03-01 00:00:00" + }, + { + "id": "2epqeqpe5qhp0qahik42q9djah", + "menuName": "微信菜单删除", + "parentId": "7d14fm1jkgijtovnkge18or2r6", + "orderNum": "4", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "wx:menu:remove", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-01 00:00:00", + "updateBy": "haiming", + "updateTime": "2018-03-01 00:00:00" + }, + { + "id": "3575kpm4gei4ipdh4mn4peg2ct", + "menuName": "微信菜单新增", + "parentId": "7d14fm1jkgijtovnkge18or2r6", + "orderNum": "2", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "wx:menu:add", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-01 00:00:00", + "updateBy": "haiming", + "updateTime": "2018-03-01 00:00:00" + }, + { + "id": "tleoggeth8jear08rs25amdtp1", + "menuName": "微信菜单查询", + "parentId": "7d14fm1jkgijtovnkge18or2r6", + "orderNum": "1", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "wx:menu:list", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-01 00:00:00", + "updateBy": "haiming", + "updateTime": "2018-03-01 00:00:00" + } + ] + }, + { + "id": "7tfveg4u38inkpjdoo8v2iki4s", + "menuName": "微信素材", + "parentId": "7bd0844c03214140a403dda2989d54f7", + "orderNum": "30", + "url": "/wx/material", + "menuType": "C", + "visible": false, + "perms": "wx:material:view", + "icon": "#", + "updateBy": "1", + "updateTime": "2019-08-08 17:26:10", + "children": [ + { + "id": "1j89195vskigvq3rn8mbgkjh90", + "menuName": "微信素材新增", + "parentId": "7tfveg4u38inkpjdoo8v2iki4s", + "orderNum": "2", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "wx:material:add", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-01 00:00:00", + "updateBy": "haiming", + "updateTime": "2018-03-01 00:00:00" + }, + { + "id": "3gl7pjf07ein3prthkop8d750i", + "menuName": "微信素材修改", + "parentId": "7tfveg4u38inkpjdoo8v2iki4s", + "orderNum": "3", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "wx:material:edit", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-01 00:00:00", + "updateBy": "haiming", + "updateTime": "2018-03-01 00:00:00" + }, + { + "id": "5aseet8o8gib9qubvs89rrfj1h", + "menuName": "微信素材删除", + "parentId": "7tfveg4u38inkpjdoo8v2iki4s", + "orderNum": "4", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "wx:material:remove", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-01 00:00:00", + "updateBy": "haiming", + "updateTime": "2018-03-01 00:00:00" + }, + { + "id": "tos8ndvbpch8ar2v3dsc9i9vg9", + "menuName": "微信素材查询", + "parentId": "7tfveg4u38inkpjdoo8v2iki4s", + "orderNum": "1", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "wx:material:list", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-01 00:00:00", + "updateBy": "haiming", + "updateTime": "2018-03-01 00:00:00" + } + ] + }, + { + "id": "q61dd1i21oji0oe2891gbn37up", + "menuName": "微信用户", + "parentId": "7bd0844c03214140a403dda2989d54f7", + "orderNum": "20", + "url": "/wx/wxUser", + "menuType": "C", + "visible": false, + "perms": "wx:wxUser:view", + "icon": "#", + "updateBy": "1", + "updateTime": "2019-06-11 10:34:54", + "children": [ + { + "id": "3le6nsatrugpkos20g28ai8b69", + "menuName": "微信用户查询", + "parentId": "q61dd1i21oji0oe2891gbn37up", + "orderNum": "1", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "wx:wxUser:list", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-01 00:00:00", + "updateBy": "haiming", + "updateTime": "2018-03-01 00:00:00" + }, + { + "id": "7jb3d4916sg0frop9kv87mbpl7", + "menuName": "微信用户同步", + "parentId": "q61dd1i21oji0oe2891gbn37up", + "orderNum": "2", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "wx:wxUser:sync", + "icon": "#", + "updateBy": "1", + "updateTime": "2019-05-14 09:49:19" + } + ] + } + ] + }, + { + "id": "9ac78f629dc74ac5877b42c73a6d4f26", + "menuName": "内容管理", + "parentId": "0", + "orderNum": "40", + "url": "", + "menuType": "M", + "visible": false, + "perms": "", + "icon": "fa fa-book", + "updateBy": "1", + "updateTime": "2019-06-11 10:31:16", + "children": [ + { + "id": "3n1cgh99rkg1ronbe4kg6fukk4", + "menuName": "文章", + "parentId": "9ac78f629dc74ac5877b42c73a6d4f26", + "orderNum": "20", + "url": "/cms/article", + "menuType": "C", + "visible": false, + "perms": "cms:article:view", + "icon": "#", + "updateBy": "1", + "updateTime": "2019-06-11 16:16:36", + "children": [ + { + "id": "4pv1d855iaj9irer5i8no4n61k", + "menuName": "文章删除", + "parentId": "3n1cgh99rkg1ronbe4kg6fukk4", + "orderNum": "4", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "cms:article:remove", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-01 00:00:00", + "updateBy": "haiming", + "updateTime": "2018-03-01 00:00:00" + }, + { + "id": "5qnj96g24kj9uq49fq3ah8fn53", + "menuName": "文章修改", + "parentId": "3n1cgh99rkg1ronbe4kg6fukk4", + "orderNum": "3", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "cms:article:edit", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-01 00:00:00", + "updateBy": "haiming", + "updateTime": "2018-03-01 00:00:00" + }, + { + "id": "64ab927r2uhjtrkqpdr7if07q9", + "menuName": "文章新增", + "parentId": "3n1cgh99rkg1ronbe4kg6fukk4", + "orderNum": "2", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "cms:article:add", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-01 00:00:00", + "updateBy": "haiming", + "updateTime": "2018-03-01 00:00:00" + }, + { + "id": "6jjag243ocir0qkn0l3fn6npq6", + "menuName": "文章查询", + "parentId": "3n1cgh99rkg1ronbe4kg6fukk4", + "orderNum": "1", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "cms:article:list", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-01 00:00:00", + "updateBy": "haiming", + "updateTime": "2018-03-01 00:00:00" + } + ] + }, + { + "id": "6bma11idlign1rrh3ngann4acf", + "menuName": "站点", + "parentId": "9ac78f629dc74ac5877b42c73a6d4f26", + "orderNum": "1", + "url": "/cms/site", + "menuType": "C", + "visible": false, + "perms": "cms:site:view", + "icon": "#", + "updateBy": "1", + "updateTime": "2019-12-13 16:29:01", + "children": [ + { + "id": "5b7v0dvpjaj72rhd4qs2dbtdnk", + "menuName": "站点查询", + "parentId": "6bma11idlign1rrh3ngann4acf", + "orderNum": "1", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "cms:site:list", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-01 00:00:00", + "updateBy": "haiming", + "updateTime": "2018-03-01 00:00:00" + }, + { + "id": "o7b4o036ikhk0q379oud2slpnp", + "menuName": "站点新增", + "parentId": "6bma11idlign1rrh3ngann4acf", + "orderNum": "2", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "cms:site:add", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-01 00:00:00", + "updateBy": "haiming", + "updateTime": "2018-03-01 00:00:00" + }, + { + "id": "o9dfvt08mog8nrdkqssa3s4qf4", + "menuName": "站点修改", + "parentId": "6bma11idlign1rrh3ngann4acf", + "orderNum": "3", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "cms:site:edit", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-01 00:00:00", + "updateBy": "haiming", + "updateTime": "2018-03-01 00:00:00" + }, + { + "id": "v83ul82ckkhgeops80fr1jmhob", + "menuName": "站点删除", + "parentId": "6bma11idlign1rrh3ngann4acf", + "orderNum": "4", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "cms:site:remove", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-01 00:00:00", + "updateBy": "haiming", + "updateTime": "2018-03-01 00:00:00" + } + ] + }, + { + "id": "q6s0e7l6dcjfmpt23h9s865rqb", + "menuName": "栏目", + "parentId": "9ac78f629dc74ac5877b42c73a6d4f26", + "orderNum": "10", + "url": "/cms/category", + "menuType": "C", + "visible": false, + "perms": "cms:category:view", + "icon": "#", + "updateBy": "1", + "updateTime": "2019-12-13 16:29:12", + "children": [ + { + "id": "1c46vji8nqgrlqa7ul078eufjt", + "menuName": "栏目删除", + "parentId": "q6s0e7l6dcjfmpt23h9s865rqb", + "orderNum": "4", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "cms:category:remove", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-01 00:00:00", + "updateBy": "haiming", + "updateTime": "2018-03-01 00:00:00" + }, + { + "id": "1r820fevh4j08rmv45a7jn5qki", + "menuName": "栏目查询", + "parentId": "q6s0e7l6dcjfmpt23h9s865rqb", + "orderNum": "1", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "cms:category:list", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-01 00:00:00", + "updateBy": "haiming", + "updateTime": "2018-03-01 00:00:00" + }, + { + "id": "4sjsbjul38g3nod9jtqlnig8mv", + "menuName": "栏目新增", + "parentId": "q6s0e7l6dcjfmpt23h9s865rqb", + "orderNum": "2", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "cms:category:add", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-01 00:00:00", + "updateBy": "haiming", + "updateTime": "2018-03-01 00:00:00" + }, + { + "id": "umkbsmd5dmgh9rb1008krur527", + "menuName": "栏目修改", + "parentId": "q6s0e7l6dcjfmpt23h9s865rqb", + "orderNum": "3", + "url": "#", + "menuType": "F", + "visible": false, + "perms": "cms:category:edit", + "icon": "#", + "remark": "", + "createBy": "admin", + "createTime": "2018-03-01 00:00:00", + "updateBy": "haiming", + "updateTime": "2018-03-01 00:00:00" + } + ] + } + ] + } +] \ No newline at end of file diff --git a/test/data/role.json b/test/data/role.json new file mode 100644 index 0000000000..78bc3055d6 --- /dev/null +++ b/test/data/role.json @@ -0,0 +1,26 @@ +[ + { + "id": "1", + "roleName": "管理员", + "roleKey": "admin", + "roleSort": "1", + "status": false, + "delFlag": false, + "flag": false, + "remark": "管理员", + "updateBy": "1", + "updateTime": "2020-03-30 17:26:37" + }, + { + "id": "2", + "roleName": "普通角色", + "roleKey": "common", + "roleSort": "2", + "status": false, + "delFlag": false, + "flag": false, + "remark": "普通角色", + "createTime": "2019-04-26 14:02:23", + "updateTime": "2019-04-26 14:02:30" + } +] \ No newline at end of file diff --git a/test/data/user.json b/test/data/user.json new file mode 100644 index 0000000000..29653e3406 --- /dev/null +++ b/test/data/user.json @@ -0,0 +1,61 @@ +[ + { + "id": "1", + "deptId": "103", + "loginName": "admin", + "userName": "小明", + "password": "vM7xT6KolcQbX7xlYfjisIJkrleH3De2t7wnmZ2zIB8=", + "salt": "6dXimQAQqHBXkhI79IRKcg==", + "email": "nutzsite@gmail.com", + "phonenumber": "18888888888", + "gender": "0", + "avatar": "b94cded00bbf4369a5cd35efb967be95", + "status": false, + "delFlag": false, + "loginIp": "0:0:0:0:0:0:0:1", + "loginDate": "2020-08-03 15:55:51", + "createBy": "admin", + "createTime": "2018-03-16 11:33:00", + "updateBy": "ry", + "updateTime": "2019-04-19 07:06:38" + }, + { + "id": "92f5c91df48644a1812c55c164716fa7", + "deptId": "108", + "loginName": "yuhaiming", + "userName": "Tom", + "password": "XTzHI41NTIG6ZbIt+PMOsaLilKMMaHouqREW53OCKIQ=", + "salt": "0EVL8dmNk7XaQiIFATsgmA==", + "email": "nutzsite@gmail.com", + "phonenumber": "18888888888", + "gender": "1", + "status": false, + "delFlag": false, + "loginIp": "127.0.0.1", + "loginDate": "2019-04-22 07:59:47", + "createBy": "1", + "createTime": "2019-04-19 07:48:19", + "updateBy": "1", + "updateTime": "2019-11-22 10:48:36" + }, + { + "id": "a14731cfa4cb4e46b1f4eb1061950c6d", + "deptId": "105", + "loginName": "haiming", + "userName": "Kricss", + "password": "DymenKivzL744ODeXSLyvkz6qnnP3qhgdtbpwZ9Txyo=", + "salt": "gA208ZKZ684tEg0Jg+UhSQ==", + "email": "nutzsite@gmail.com", + "phonenumber": "18888888888", + "gender": "0", + "avatar": "9acacf18f51c49df8be15b072e9a30c3", + "status": false, + "delFlag": false, + "loginIp": "0:0:0:0:0:0:0:1", + "loginDate": "2019-12-10 15:08:57", + "createBy": "1", + "createTime": "2019-04-26 03:00:24", + "updateBy": "1", + "updateTime": "2019-12-10 15:02:20" + } +] \ No newline at end of file diff --git a/test/org/nutz/AdvancedTestAll.java b/test/org/nutz/AdvancedTestAll.java index cdbaeee40e..8ccef0f406 100644 --- a/test/org/nutz/AdvancedTestAll.java +++ b/test/org/nutz/AdvancedTestAll.java @@ -10,9 +10,6 @@ import java.util.List; import java.util.Map; -import junit.framework.TestFailure; -import junit.framework.TestResult; - import org.junit.Test; import org.junit.runner.Description; import org.junit.runner.Request; @@ -22,6 +19,9 @@ import org.nutz.lang.Lang; import org.nutz.resource.Scans; +import junit.framework.TestFailure; +import junit.framework.TestResult; + /** * 以多种顺序执行TestCase * @author wendal diff --git a/test/org/nutz/AllWithDB.java b/test/org/nutz/AllWithDB.java index a8da3bf2fb..53b387e927 100644 --- a/test/org/nutz/AllWithDB.java +++ b/test/org/nutz/AllWithDB.java @@ -1,7 +1,6 @@ package org.nutz; import org.junit.runner.RunWith; - import org.junit.runners.Suite; import org.nutz.dao.AllDao; import org.nutz.trans.AllTrans; diff --git a/test/org/nutz/Nutzs.java b/test/org/nutz/Nutzs.java index 91765d1531..44d13fce11 100644 --- a/test/org/nutz/Nutzs.java +++ b/test/org/nutz/Nutzs.java @@ -1,15 +1,15 @@ package org.nutz; +import static java.lang.String.format; + import java.io.File; import java.io.InputStream; import java.security.AccessController; -import java.util.HashMap; import java.security.PrivilegedAction; +import java.util.HashMap; import java.util.Map; import java.util.Properties; -import java.util.concurrent.atomic.AtomicLong; -import org.nutz.aop.AbstractClassAgent; import org.nutz.aop.ClassDefiner; import org.nutz.aop.DefaultClassDefiner; import org.nutz.dao.DatabaseMeta; @@ -21,8 +21,6 @@ import org.nutz.lang.Streams; import org.nutz.lang.Strings; -import static java.lang.String.*; - public class Nutzs { private static Properties pp; @@ -110,11 +108,7 @@ public static void notSupport(DatabaseMeta meta) { * 调用此方法将改变AOP类名命名规则 * @return */ - @SuppressWarnings("deprecation") public static ClassDefiner cd() { - if (AbstractClassAgent.t == null) - AbstractClassAgent.t = new AtomicLong(8); - AbstractClassAgent.t.incrementAndGet(); return AccessController.doPrivileged(new PrivilegedAction() { public DefaultClassDefiner run() { return new DefaultClassDefiner(); diff --git a/test/org/nutz/aop/asm/AsmClassAgentTest.java b/test/org/nutz/aop/asm/AsmClassAgentTest.java index 8058a76ea1..29b3467709 100644 --- a/test/org/nutz/aop/asm/AsmClassAgentTest.java +++ b/test/org/nutz/aop/asm/AsmClassAgentTest.java @@ -29,15 +29,17 @@ import org.nutz.aop.matcher.MethodMatcherFactory; import org.nutz.json.Json; import org.nutz.lang.Mirror; +import org.nutz.lang.random.R; public class AsmClassAgentTest { @Test public void test_duplicate_class_exception() throws Exception { int[] cc = new int[4]; - ClassAgent ca = getNewClassAgent(); + AsmClassAgent ca = getNewClassAgent(); ca.addInterceptor(MethodMatcherFactory.matcher(".*"), new MethodCounter(cc)); - ClassAgent ca2 = getNewClassAgent(); + AsmClassAgent ca2 = getNewClassAgent(); + ca2.id = ca.id; ca2.addInterceptor(MethodMatcherFactory.matcher(".*"), new MethodCounter(cc)); ClassDefiner cd = Nutzs.cd(); @@ -159,8 +161,9 @@ public void test_basice_matcher_by_mod() { assertEquals("[2, 2, 0, 0]", Json.toJson(cpro)); } - public ClassAgent getNewClassAgent() { - ClassAgent classAgent = new AsmClassAgent(); + public AsmClassAgent getNewClassAgent() { + AsmClassAgent classAgent = new AsmClassAgent(); + classAgent.id = R.UU32(); classAgent.addInterceptor(MethodMatcherFactory.matcher(".*"), new LoggingMethodInterceptor()); return classAgent; } diff --git a/test/org/nutz/aop/asm/RegexMethodMatcherTest.java b/test/org/nutz/aop/asm/RegexMethodMatcherTest.java index 2a458457ce..3acfa52d0d 100644 --- a/test/org/nutz/aop/asm/RegexMethodMatcherTest.java +++ b/test/org/nutz/aop/asm/RegexMethodMatcherTest.java @@ -13,12 +13,14 @@ import org.nutz.aop.matcher.MethodMatcherFactory; import org.nutz.aop.matcher.RegexMethodMatcher; import org.nutz.lang.Mirror; +import org.nutz.lang.random.R; public class RegexMethodMatcherTest { @Test public void testRegexMethodMatcherStringStringInt() throws Throwable { AsmClassAgent agent = new AsmClassAgent(); + agent.id = R.UU32(); MyL interceptor = new MyL(); agent.addInterceptor(new RegexMethodMatcher(null, "nonArgsVoid", 0), interceptor); agent.addInterceptor(MethodMatcherFactory.matcher(".*"), new LoggingMethodInterceptor()); diff --git a/test/org/nutz/castor/CastorTest.java b/test/org/nutz/castor/CastorTest.java index 587a0326df..a0da0cd332 100644 --- a/test/org/nutz/castor/CastorTest.java +++ b/test/org/nutz/castor/CastorTest.java @@ -2,6 +2,9 @@ import java.io.File; import java.sql.Timestamp; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; @@ -507,4 +510,29 @@ public void testString2Enum() { Assert.assertEquals(AlipayNotifyType.StatusSync, Castors.create().castTo("trade_status_sync", AlipayNotifyType.class)); } + +// @Test +// public void test_locale_date_time() { +// { +// LocalDateTime dt = Castors.me().castTo("2018-02-20 21:11:51", LocalDateTime.class); +// String tmp = Castors.me().castToString(dt); +// LocalDateTime dt2 = Castors.me().castTo(tmp, LocalDateTime.class); +// assertEquals(dt, dt2); +// System.out.println(tmp); +// } +// { +// LocalTime dt = LocalTime.now(); +// String tmp = Castors.me().castToString(dt); +// LocalTime dt2 = Castors.me().castTo(tmp, LocalTime.class); +// assertEquals(dt, dt2); +// System.out.println(tmp); +// } +// { +// LocalDate dt = LocalDate.now(); +// String tmp = Castors.me().castToString(dt); +// LocalDate dt2 = Castors.me().castTo(tmp, LocalDate.class); +// assertEquals(dt, dt2); +// System.out.println(tmp); +// } +// } } diff --git a/test/org/nutz/castor/castor/String2NumberTest.java b/test/org/nutz/castor/castor/String2NumberTest.java new file mode 100644 index 0000000000..0184bd6e53 --- /dev/null +++ b/test/org/nutz/castor/castor/String2NumberTest.java @@ -0,0 +1,48 @@ +package org.nutz.castor.castor; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class String2NumberTest { + + private String2Number string2Number = new String2Number() { + + @Override + protected Integer getPrimitiveDefaultValue() { + return 0; + } + + @Override + protected Integer getFalseValue() { + return falseValue; + } + + @Override + protected Integer getTrueValue() { + return trueValue; + } + + @Override + protected Integer valueOf(final String str) { + return Integer.valueOf(str); + } + + }; + private Integer falseValue = Integer.valueOf(0); + private Integer trueValue = Integer.valueOf(1); + + @Test + public void testTrueValue() { + + assertEquals(string2Number.cast("true", Integer.class), this.trueValue); + + } + + @Test + public void testFalseValue() { + + assertEquals(string2Number.cast("false", Integer.class), this.falseValue); + + } +} diff --git a/test/org/nutz/dao/AllDao.java b/test/org/nutz/dao/AllDao.java index a489221380..d9e6cd7195 100644 --- a/test/org/nutz/dao/AllDao.java +++ b/test/org/nutz/dao/AllDao.java @@ -14,6 +14,7 @@ import org.nutz.dao.test.sqls.AllSqls; import org.nutz.dao.texp.ChainTest; import org.nutz.dao.texp.CndTest; +import org.nutz.dao.texp.LamdaCndTest; /** * Prepare a database with URL: jdbc:mysql://localhost:3306/zzhtest support user @@ -30,6 +31,7 @@ AllMapping.class, AllNormal.class, CndTest.class, + LamdaCndTest.class, ChainTest.class, SqlLiteralTest.class, AllDaoExec.class, diff --git a/test/org/nutz/dao/CrudLinkTest.java b/test/org/nutz/dao/CrudLinkTest.java new file mode 100644 index 0000000000..a199e636fb --- /dev/null +++ b/test/org/nutz/dao/CrudLinkTest.java @@ -0,0 +1,329 @@ +package org.nutz.dao; + +import org.junit.Test; +import org.nutz.dao.test.DaoCase; +import org.nutz.dao.test.meta.TestMenu; +import org.nutz.dao.test.meta.TestRole; +import org.nutz.dao.util.TestUtil; +import org.nutz.json.Json; +import org.nutz.lang.random.R; + +import java.util.List; + +import static org.junit.Assert.*; + +/** + * @author Haiming + * @date 2020/8/14 18:50 AM + */ +public class CrudLinkTest extends DaoCase { + + @Test + public void insertWithTest() { + + dao.create(TestMenu.class, true); + dao.create(TestRole.class, true); + String menuJson = TestUtil.getFileData("data/menu.json"); + List menuList = Json.fromJsonAsList(TestMenu.class, menuJson); + String roleJson = TestUtil.getFileData("data/role.json"); + List roleList = Json.fromJsonAsList(TestRole.class, roleJson); + for (TestRole role : roleList) { + role.setId(R.UU32().toLowerCase()); + if ("admin".equals(role.getRoleKey())) { + role.setMenus(menuList); + dao.insertWith(role, "menus"); + } else { + dao.fastInsert(role); + } + } + TestRole testRole = dao.fetch(TestRole.class, Cnd.NEW().and("role_key", "=", "admin")); + testRole = dao.fetchLinks(testRole, "menus"); + assertNotNull(testRole); + assertNotNull(testRole.getMenus()); + } + + + @Test + public void insertLinksTest() { + + dao.create(TestMenu.class, true); + dao.create(TestRole.class, true); + String menuJson = TestUtil.getFileData("data/menu.json"); + List menuList = Json.fromJsonAsList(TestMenu.class, menuJson); + String roleJson = TestUtil.getFileData("data/role.json"); + List roleList = Json.fromJsonAsList(TestRole.class, roleJson); + for (TestRole role : roleList) { + role.setId(R.UU32().toLowerCase()); + dao.fastInsert(role); + if ("admin".equals(role.getRoleKey())) { + role.setMenus(menuList); + dao.insertLinks(role, "menus"); + } + } + TestRole testRole = dao.fetch(TestRole.class, Cnd.NEW().and("role_key", "=", "admin")); + testRole = dao.fetchLinks(testRole, "menus"); + assertNotNull(testRole.getMenus()); + } + + + @Test + public void insertRelationTest() { + + dao.create(TestMenu.class, true); + dao.create(TestRole.class, true); + String menuJson = TestUtil.getFileData("data/menu.json"); + List menuList = Json.fromJsonAsList(TestMenu.class, menuJson); + for (TestMenu menu : menuList) { + dao.fastInsert(menu); + } + String roleJson = TestUtil.getFileData("data/role.json"); + List roleList = Json.fromJsonAsList(TestRole.class, roleJson); + for (TestRole role : roleList) { + role.setId(R.UU32().toLowerCase()); + dao.fastInsert(role); + if ("admin".equals(role.getRoleKey())) { + role.setMenus(menuList); + dao.insertRelation(role, "menus"); + } + } + TestRole testRole = dao.fetch(TestRole.class, Cnd.NEW().and("role_key", "=", "admin")); + testRole = dao.fetchLinks(testRole, "menus"); + assertNotNull(testRole.getMenus()); + } + + @Test + public void updateWithTest() { + + dao.create(TestMenu.class, true); + dao.create(TestRole.class, true); + String menuJson = TestUtil.getFileData("data/menu.json"); + List menuList = Json.fromJsonAsList(TestMenu.class, menuJson); + String roleJson = TestUtil.getFileData("data/role.json"); + List roleList = Json.fromJsonAsList(TestRole.class, roleJson); + for (TestRole role : roleList) { + role.setId(R.UU32().toLowerCase()); + if ("admin".equals(role.getRoleKey())) { + role.setMenus(menuList); + dao.insertWith(role, "menus"); + } else { + dao.fastInsert(role); + } + } + TestRole testRole = dao.fetch(TestRole.class, Cnd.NEW().and("role_key", "=", "admin")); + testRole = dao.fetchLinks(testRole, "menus"); + testRole.setRoleName("hhhhh"); + TestMenu tmpMenu = testRole.getMenus().get(0); + for(TestMenu m:testRole.getMenus()){ + if(tmpMenu.getId().equals(m.getId())){ + m.setMenuName("test"); + } + } + dao.updateWith(testRole, "menus"); + testRole = dao.fetch(TestRole.class, Cnd.NEW().and("role_key", "=", "admin")); + assertEquals("hhhhh",testRole.getRoleName()); + TestMenu testMenu = dao.fetch(TestMenu.class, tmpMenu.getId()); + assertEquals("test",testMenu.getMenuName()); + } + + + @Test + public void updateLinksTest() { + + dao.create(TestMenu.class, true); + dao.create(TestRole.class, true); + String menuJson = TestUtil.getFileData("data/menu.json"); + List menuList = Json.fromJsonAsList(TestMenu.class, menuJson); + String roleJson = TestUtil.getFileData("data/role.json"); + List roleList = Json.fromJsonAsList(TestRole.class, roleJson); + for (TestRole role : roleList) { + role.setId(R.UU32().toLowerCase()); + if ("admin".equals(role.getRoleKey())) { + role.setMenus(menuList); + dao.insertWith(role, "menus"); + } else { + dao.fastInsert(role); + } + } + TestRole testRole = dao.fetch(TestRole.class, Cnd.NEW().and("role_key", "=", "admin")); + testRole = dao.fetchLinks(testRole, "menus"); + TestMenu tmpMenu = testRole.getMenus().get(0); + for(TestMenu m:testRole.getMenus()){ + if(tmpMenu.getId().equals(m.getId())){ + m.setMenuName("test"); + } + } + dao.updateLinks(testRole, "menus"); + TestMenu testMenu = dao.fetch(TestMenu.class, tmpMenu.getId()); + assertEquals("test",testMenu.getMenuName()); + } + + @Test + public void deleteWithTest() { + + dao.create(TestMenu.class, true); + dao.create(TestRole.class, true); + String menuJson = TestUtil.getFileData("data/menu.json"); + List menuList = Json.fromJsonAsList(TestMenu.class, menuJson); + for (TestMenu menu : menuList) { + dao.fastInsert(menu); + } + String roleJson = TestUtil.getFileData("data/role.json"); + List roleList = Json.fromJsonAsList(TestRole.class, roleJson); + for (TestRole role : roleList) { + role.setId(R.UU32().toLowerCase()); + dao.fastInsert(role); + if ("admin".equals(role.getRoleKey())) { + role.setMenus(menuList); + dao.insertRelation(role, "menus"); + } + } + TestRole testRole = dao.fetch(TestRole.class, Cnd.NEW().and("role_key", "=", "admin")); + testRole = dao.fetchLinks(testRole, "menus"); + + dao.deleteWith(testRole, "menus"); + testRole = dao.fetch(TestRole.class, Cnd.NEW().and("role_key", "=", "admin")); + assertNull(testRole); + for(TestMenu m:menuList){ + TestMenu tmp =dao.fetch(TestMenu.class,m.getId()); + assertNull(tmp); + } + } + + + @Test + public void deleteLinksTest() { + + dao.create(TestMenu.class, true); + dao.create(TestRole.class, true); + String menuJson = TestUtil.getFileData("data/menu.json"); + List menuList = Json.fromJsonAsList(TestMenu.class, menuJson); + for (TestMenu menu : menuList) { + dao.fastInsert(menu); + } + String roleJson = TestUtil.getFileData("data/role.json"); + List roleList = Json.fromJsonAsList(TestRole.class, roleJson); + for (TestRole role : roleList) { + role.setId(R.UU32().toLowerCase()); + dao.fastInsert(role); + if ("admin".equals(role.getRoleKey())) { + role.setMenus(menuList); + dao.insertRelation(role, "menus"); + } + } + TestRole testRole = dao.fetch(TestRole.class, Cnd.NEW().and("role_key", "=", "admin")); + testRole = dao.fetchLinks(testRole, "menus"); + + dao.deleteLinks(testRole, "menus"); + testRole = dao.fetch(TestRole.class, Cnd.NEW().and("role_key", "=", "admin")); + assertNotNull(testRole); + for(TestMenu m:menuList){ + TestMenu tmp =dao.fetch(TestMenu.class,m.getId()); + assertNull(tmp); + } + } + + @Test + public void clearLinksTest() { + dao.create(TestMenu.class, true); + dao.create(TestRole.class, true); + String menuJson = TestUtil.getFileData("data/menu.json"); + List menuList = Json.fromJsonAsList(TestMenu.class, menuJson); + for (TestMenu menu : menuList) { + dao.fastInsert(menu); + } + String roleJson = TestUtil.getFileData("data/role.json"); + List roleList = Json.fromJsonAsList(TestRole.class, roleJson); + for (TestRole role : roleList) { + role.setId(R.UU32().toLowerCase()); + dao.fastInsert(role); + if ("admin".equals(role.getRoleKey())) { + role.setMenus(menuList); + dao.insertRelation(role, "menus"); + } + } + TestRole testRole = dao.fetch(TestRole.class, Cnd.NEW().and("role_key", "=", "admin")); + testRole = dao.fetchLinks(testRole, "menus"); + + dao.clearLinks(testRole, "menus"); + testRole = dao.fetch(TestRole.class, Cnd.NEW().and("role_key", "=", "admin")); + assertNotNull(testRole); + assertNull(testRole.getMenus()); + for(TestMenu m:menuList){ + TestMenu tmp =dao.fetch(TestMenu.class,m.getId()); + assertNotNull(tmp); + } + } + + @Test + public void fetchByJoinTest() { + dao.create(TestMenu.class, true); + dao.create(TestRole.class, true); + String menuJson = TestUtil.getFileData("data/menu.json"); + List menuList = Json.fromJsonAsList(TestMenu.class, menuJson); + for (TestMenu menu : menuList) { + dao.fastInsert(menu); + } + String roleJson = TestUtil.getFileData("data/role.json"); + List roleList = Json.fromJsonAsList(TestRole.class, roleJson); + for (TestRole role : roleList) { + role.setId(R.UU32().toLowerCase()); + dao.fastInsert(role); + if ("admin".equals(role.getRoleKey())) { + role.setMenus(menuList); + dao.insertRelation(role, "menus"); + } + } + TestRole testRole = dao.fetchByJoin(TestRole.class,"menus", Cnd.NEW().and("role_key", "=", "admin")); + assertNotNull(testRole); + assertNotNull(testRole.getMenus()); + } + + @Test + public void queryByJoinTest() { + dao.create(TestMenu.class, true); + dao.create(TestRole.class, true); + String menuJson = TestUtil.getFileData("data/menu.json"); + List menuList = Json.fromJsonAsList(TestMenu.class, menuJson); + for (TestMenu menu : menuList) { + dao.fastInsert(menu); + } + String roleJson = TestUtil.getFileData("data/role.json"); + List roleList = Json.fromJsonAsList(TestRole.class, roleJson); + for (TestRole role : roleList) { + role.setId(R.UU32().toLowerCase()); + dao.fastInsert(role); + if ("admin".equals(role.getRoleKey())) { + role.setMenus(menuList); + dao.insertRelation(role, "menus"); + } + } + List testRoles = dao.queryByJoin(TestRole.class,"menus", Cnd.NEW().and("role_key", "=", "admin")); + for(TestRole r:testRoles){ + assertNotNull(r); + assertNotNull(r.getMenus()); + } + } + + @Test + public void countByJoinTest() { + dao.create(TestMenu.class, true); + dao.create(TestRole.class, true); + String menuJson = TestUtil.getFileData("data/menu.json"); + List menuList = Json.fromJsonAsList(TestMenu.class, menuJson); + for (TestMenu menu : menuList) { + dao.fastInsert(menu); + } + String roleJson = TestUtil.getFileData("data/role.json"); + List roleList = Json.fromJsonAsList(TestRole.class, roleJson); + for (TestRole role : roleList) { + role.setId(R.UU32().toLowerCase()); + dao.fastInsert(role); + if ("admin".equals(role.getRoleKey())) { + role.setMenus(menuList); + dao.insertRelation(role, "menus"); + } + } + int data = dao.countByJoin(TestRole.class,"menus", Cnd.NEW().and("role_key", "=", "admin")); + assertTrue(data > 0); + } +} diff --git a/test/org/nutz/dao/QueryResultTest.java b/test/org/nutz/dao/QueryResultTest.java new file mode 100644 index 0000000000..ce359345a9 --- /dev/null +++ b/test/org/nutz/dao/QueryResultTest.java @@ -0,0 +1,103 @@ +package org.nutz.dao; + +import java.util.List; + +import org.junit.Test; +import org.nutz.dao.pager.Pager; +import org.nutz.dao.test.DaoCase; +import org.nutz.dao.test.meta.TestMenu; +import org.nutz.dao.test.meta.TestRole; +import org.nutz.dao.test.meta.TestUser; +import org.nutz.dao.util.TestUtil; +import org.nutz.json.Json; +import org.nutz.lang.random.R; + +import static org.junit.Assert.*; + +/** + * @Author: Haimming + * @Date: 2020-01-16 16:47 + * @Version 1.0 + */ +public class QueryResultTest extends DaoCase { + + @Test + public void queryResultTest() { + // 若必要的数据不存在,则初始化数据库 + dao.create(TestMenu.class, true); + String menuJson = TestUtil.getFileData("data/menu.json"); + List menuList = Json.fromJsonAsList(TestMenu.class,menuJson); + for(TestMenu menu:menuList){ + dao.fastInsert(menu); + } + Pager pager = dao.createPager(0, 10); + Cnd cnd = Cnd.NEW(); + cnd.orderBy("menu_name", "asc"); + pager.setRecordCount(dao.count(TestMenu.class, cnd)); + List list = dao.query(TestMenu.class, cnd, pager); + pager.setRecordCount(dao.count(TestMenu.class, cnd)); + System.out.println(pager.toString()); + QueryResult result = new QueryResult(list, pager); + assertNotNull(result); + assertNotNull(result.getList()); + assertNotNull(result.convertList(Json.class)); + assertNotNull(result.getPager()); + result.setList(null); + assertNull(result.getList()); + result.setPager(null); + assertNull(result.getPager()); + + } + + + @Test + public void crudTest() { + // 若必要的数据不存在,则初始化数据库 + dao.create(TestMenu.class, true); + dao.create(TestRole.class, true); + dao.create(TestUser.class, true); + String menuJson = TestUtil.getFileData("data/menu.json"); + List menuList = Json.fromJsonAsList(TestMenu.class,menuJson); + for(TestMenu menu:menuList){ + dao.fastInsert(menu); + } + List testMenus = dao.query(TestMenu.class, Cnd.NEW()); + assertNotNull(testMenus); + + String roleJson = TestUtil.getFileData("data/role.json"); + List roleList = Json.fromJsonAsList(TestRole.class,roleJson); + for(TestRole role:roleList){ + role.setId(R.UU32().toLowerCase()); + dao.fastInsert(role); + if("admin".equals(role.getRoleKey())){ + role.setMenus(menuList); + dao.insertRelation(role, "menus"); + } + } +// List testRoles = dao.query(TestRole.class, Cnd.NEW()); + TestRole testRole =dao.fetch(TestRole.class,Cnd.NEW().and("role_key","=","admin")); + testRole =dao.fetchLinks(testRole,"menus"); + assertNotNull(testRole); + assertNotNull(testRole.getMenus()); + + String userJson = TestUtil.getFileData("data/user.json"); + List userList = Json.fromJsonAsList(TestUser.class,userJson); + for(TestUser user:userList){ + user.setId(R.UU32().toLowerCase()); +// dao.fastInsert(user); + if("admin".equals(user.getLoginName())){ + user.setRoles(roleList); + dao.insertWith(user, "roles"); + }else { + dao.fastInsert(user); + } + } + TestUser testUser = dao.fetch(TestUser.class,Cnd.NEW().and("login_name","=","admin")); + testUser =dao.fetchLinks(testUser,"roles"); + assertNotNull(testUser); + assertNotNull(testUser.getRoles()); + testUser =dao.clearLinks(testUser,"roles"); + testUser =dao.fetchLinks(testUser,"roles"); + assertTrue(testUser.getRoles().size() ==0); + } +} diff --git a/test/org/nutz/dao/SqlNotFoundExceptionTest.java b/test/org/nutz/dao/SqlNotFoundExceptionTest.java new file mode 100644 index 0000000000..e40f42f306 --- /dev/null +++ b/test/org/nutz/dao/SqlNotFoundExceptionTest.java @@ -0,0 +1,25 @@ +package org.nutz.dao; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +/** + * @Author: Haimming + * @Date: 2020-01-15 10:58 + * @Version 1.0 + */ +public class SqlNotFoundExceptionTest { + + @Test + public void sqlNotFoundExceptionTest() { + try { + throw new SqlNotFoundException("key"); + } + catch (Exception e) { + System.out.println(e.getMessage()); + assertEquals("fail to find SQL 'key'!", e.getMessage()); + } + } + +} diff --git a/test/org/nutz/dao/impl/sql/NutPojoMakerTest.java b/test/org/nutz/dao/impl/sql/NutPojoMakerTest.java new file mode 100644 index 0000000000..95d9bc91af --- /dev/null +++ b/test/org/nutz/dao/impl/sql/NutPojoMakerTest.java @@ -0,0 +1,46 @@ +package org.nutz.dao.impl.sql; + +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; + +import org.junit.Test; +import org.nutz.dao.entity.Entity; +import org.nutz.dao.sql.Pojo; +import org.nutz.dao.sql.PojoMaker; +import org.nutz.dao.test.DaoCase; +import org.nutz.dao.test.meta.Master; +import org.nutz.dao.test.meta.Pet; +import org.nutz.lang.Lang; + +/** + * @Author: Haimming + * @Date: 2019-12-19 10:24 + * @Version 1.0 + */ +public class NutPojoMakerTest extends DaoCase { + private Entity en(Class type) { + return dao.getEntity(type); + } + + @Test + public void makeQueryByJoin() { + dao.create(Pet.class, true); + dao.create(Master.class, true); + Master master = new Master(); + master.setName("zozoh"); + + Pet petA = new Pet(); + petA.setName("wendal"); + petA.setAge(31); + Pet petB = new Pet(); + petB.setName("pangwu"); + petB.setAge(30); + master.setPets(Arrays.asList(petA, petB)); + dao.insertWith(master, null); + PojoMaker pojoMaker = new NutPojoMaker(dao.getJdbcExpert()); + Entity en = en(Master.class); + Pojo pojo = pojoMaker.makeQueryByJoin(en, "pets"); + assertEquals(true, Lang.isNotEmpty(pojo)); + } +} \ No newline at end of file diff --git a/test/org/nutz/dao/test/entity/RecordTest.java b/test/org/nutz/dao/test/entity/RecordTest.java new file mode 100644 index 0000000000..08b0fdc086 --- /dev/null +++ b/test/org/nutz/dao/test/entity/RecordTest.java @@ -0,0 +1,91 @@ +package org.nutz.dao.test.entity; + +import org.junit.Assert; +import org.junit.Test; +import org.nutz.dao.entity.Record; + +import java.util.Set; + +public class RecordTest { + @Test + public void testRecord2Pojo(){ + Record record = new Record(); + record.set("loginName","loginName"); + record.set("nickName","nickName"); + + TPojo pojo = record.toPojo(TPojo.class); + Assert.assertEquals(pojo.loginName,"loginName"); + } + + public static class TPojo{ + private String loginName; + private String nickName; + + public String getLoginName() { + return loginName; + } + + public void setLoginName(String loginName) { + this.loginName = loginName; + } + + public String getNickName() { + return nickName; + } + + public void setNickName(String nickName) { + this.nickName = nickName; + } + } + + + @Test + public void testRecordRemove(){ + Record record = new Record(); + record.set("loginName","loginName"); + record.set("nickName","nickName"); + TPojo pojo = record.toPojo(TPojo.class); + Assert.assertEquals(pojo.loginName,"loginName"); + record.remove("loginName"); + pojo = record.toPojo(TPojo.class); + Assert.assertEquals(pojo.loginName,null); + } + + @Test + public void getColumnNamesTest() { + Record record = new Record(); + record.set("loginName","loginName"); + record.set("nickName","nickName"); + Set column = record.getColumnNames(); + System.out.println(column.toString()); + Assert.assertEquals(column.toString(),"[loginname, nickname]"); + } + + @Test + public void getTest() { + Record record = new Record(); + record.set("loginName","loginName"); + record.set("age",18); + record.set("long",500); + record.set("double",10.1); + record.set("year","testests"); + + int age = record.getInt("age"); + int year = record.getInt("year",2000); + int dft = record.getInt("dft",30); + double double1 = record.getDouble("double"); + double double2 = record.getDouble("double1",100.1); + long long1 = record.getLong("long"); + long long2 = record.getLong("long1",600); + Assert.assertEquals(age,18); + Assert.assertEquals(year,2000); + Assert.assertEquals(dft,30); +// System.out.println(Double.compare(double1,10.1)); + Assert.assertEquals(0,Double.compare(double1,10.1)); + Assert.assertEquals(0,Double.compare(double2,100.1)); + Assert.assertEquals(long1,500); + Assert.assertEquals(long2,600); + } + + +} diff --git a/test/org/nutz/dao/test/interceptor/AllDaoInterceptorTest.java b/test/org/nutz/dao/test/interceptor/AllDaoInterceptorTest.java index 2860dd1fab..4967047bc4 100644 --- a/test/org/nutz/dao/test/interceptor/AllDaoInterceptorTest.java +++ b/test/org/nutz/dao/test/interceptor/AllDaoInterceptorTest.java @@ -6,7 +6,8 @@ @RunWith(Suite.class) @SuiteClasses({ - SimpleDaoInterceptorTest.class + SimpleDaoInterceptorTest.class, + SimplePojoInterceptorTest.class }) public class AllDaoInterceptorTest { diff --git a/test/org/nutz/dao/test/interceptor/SimplePojoInterceptorTest.java b/test/org/nutz/dao/test/interceptor/SimplePojoInterceptorTest.java new file mode 100644 index 0000000000..bf5ce579be --- /dev/null +++ b/test/org/nutz/dao/test/interceptor/SimplePojoInterceptorTest.java @@ -0,0 +1,35 @@ +package org.nutz.dao.test.interceptor; + +import org.junit.Test; +import org.nutz.dao.Sqls; +import org.nutz.dao.sql.Sql; +import org.nutz.dao.test.DaoCase; +import org.nutz.dao.test.meta.XPet; +import org.nutz.lang.Lang; + +import static org.junit.Assert.*; +import static org.junit.Assert.assertNotNull; + +public class SimplePojoInterceptorTest extends DaoCase { + + @Test + public void test_pojo_interceptor_anno() { + String name="huchuc@vip.qq.com"; + dao.create(XPet.class, true); + XPet xPet = new XPet(); + //主动设置name值,PrevInsert nullEffective=true,实际name已有预设值则PrevInsert不会起效 + xPet.setName(name); + dao.insert(xPet); + assertEquals(1, dao.count(XPet.class)); + assertNotNull(dao.fetch(XPet.class)); + Sql sql = Sqls.fetchEntity("select * from t_xpet"); + sql.setEntity(dao.getEntity(XPet.class)); + dao.execute(sql); + XPet xPet1 = sql.getObject(XPet.class); + assertEquals(xPet1.getName(),name); + assertNotNull(sql.getObject(XPet.class)); + assertNull(sql.getObject(XPet.class).getOtherTime()); + Lang.quiteSleep(1000); + dao.update(sql.getObject(XPet.class), "updateTime"); + } +} diff --git a/test/org/nutz/dao/test/meta/BaseBean.java b/test/org/nutz/dao/test/meta/BaseBean.java new file mode 100644 index 0000000000..454525fb8d --- /dev/null +++ b/test/org/nutz/dao/test/meta/BaseBean.java @@ -0,0 +1,22 @@ +package org.nutz.dao.test.meta; + +import org.nutz.lang.random.R; + +import java.io.Serializable; +import java.util.Date; + +/** + * @author : haiming + * @date : 2020-02-27 + */ +public abstract class BaseBean implements Serializable { + private static final long serialVersionUID = 1L; + + public String uuid() { + return R.UU32().toLowerCase(); + } + + public Date now() { + return new Date(); + } +} diff --git a/test/org/nutz/dao/test/meta/Platoon.java b/test/org/nutz/dao/test/meta/Platoon.java index aa0a7d5b92..1c11157c61 100644 --- a/test/org/nutz/dao/test/meta/Platoon.java +++ b/test/org/nutz/dao/test/meta/Platoon.java @@ -32,9 +32,15 @@ public static Platoon make(Base base, String name) { @Column("leader") private String leaderName; + + @Column("leader2") + private String leaderName2; @One(target = Soldier.class, field = "leaderName") private Soldier leader; + + @One(target = Soldier.class, field = "leaderName2") + private Soldier leader2; @Many(target = Soldier.class, field = "") private List soliders; @@ -111,4 +117,19 @@ public void setTanks(Map tanks) { this.tanks = tanks; } + public void setLeader2(Soldier leader2) { + this.leader2 = leader2; + } + + public void setLeaderName2(String leaderName2) { + this.leaderName2 = leaderName2; + } + + public Soldier getLeader2() { + return leader2; + } + + public String getLeaderName2() { + return leaderName2; + } } diff --git a/test/org/nutz/dao/test/meta/PojoWithInteger.java b/test/org/nutz/dao/test/meta/PojoWithInteger.java new file mode 100644 index 0000000000..34618cd062 --- /dev/null +++ b/test/org/nutz/dao/test/meta/PojoWithInteger.java @@ -0,0 +1,39 @@ +package org.nutz.dao.test.meta; + +import org.nutz.dao.entity.annotation.Name; +import org.nutz.dao.entity.annotation.Table; + +@Table("t_pojo_with_integer") +public class PojoWithInteger { + + @Name + private String name; + + private int age; + + private Integer t; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + public Integer getT() { + return t; + } + + public void setT(Integer t) { + this.t = t; + } +} diff --git a/test/org/nutz/dao/test/meta/TestImage.java b/test/org/nutz/dao/test/meta/TestImage.java new file mode 100755 index 0000000000..5f3008fb48 --- /dev/null +++ b/test/org/nutz/dao/test/meta/TestImage.java @@ -0,0 +1,103 @@ +package org.nutz.dao.test.meta; + +import org.nutz.dao.entity.annotation.*; + +import java.io.Serializable; + +/** + * 图片管理表 sys_image + * + * @author haiming + * @date 2019-05-09 + */ +@Table("test_image" ) +public class TestImage extends BaseBean implements Serializable { + private static final long serialVersionUID = 1L; + + @Name + @Column("id" ) + @Comment("id " ) + @ColDefine(type = ColType.VARCHAR, width = 64) + @Prev(els = {@EL("uuid()")}) + private String id; + + /** + * 图片类型 + */ + @Column("photo_type" ) + @Comment("图片类型 " ) + private String photoType; + + /** + * 数据 + */ + @Column("base64" ) + @Comment("数据 " ) + @ColDefine(type = ColType.VARCHAR, width = 2000) + private String base64; + + /** + * 本地地址 + */ + @Column("local_path" ) + @Comment("本地地址 " ) + private String localPath; + + /** + * 网页地址 + */ + @Column("url" ) + @Comment("网页地址 " ) + private String url; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getBase64() { + return base64; + } + + public void setBase64(String base64) { + this.base64 = base64; + } + + public String getLocalPath() { + return localPath; + } + + public void setLocalPath(String localPath) { + this.localPath = localPath; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getPhotoType() { + return photoType; + } + + public void setPhotoType(String photoType) { + this.photoType = photoType; + } + + @Override + public String toString() { + return "TestImage{" + + "id='" + id + '\'' + + ", photoType='" + photoType + '\'' + + ", base64='" + base64 + '\'' + + ", localPath='" + localPath + '\'' + + ", url='" + url + '\'' + + '}'; + } +} diff --git a/test/org/nutz/dao/test/meta/TestMenu.java b/test/org/nutz/dao/test/meta/TestMenu.java new file mode 100644 index 0000000000..99f5434200 --- /dev/null +++ b/test/org/nutz/dao/test/meta/TestMenu.java @@ -0,0 +1,244 @@ +package org.nutz.dao.test.meta; + +import org.nutz.dao.entity.annotation.*; +import org.nutz.lang.Lang; +import org.nutz.lang.Strings; +import org.nutz.lang.random.R; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * @author Hamming_Yu on 2018/12/29. + */ +@Table("test_menu") +public class TestMenu extends BaseBean implements Serializable { + private static final long serialVersionUID = 1L; + + @Column + @Name + @Comment("ID") + @ColDefine(type = ColType.VARCHAR, width = 64) + @Prev(els = {@EL("uuid()")}) + private String id; + + @Column("menu_name") + @Comment("菜单名称") + @ColDefine(type = ColType.VARCHAR, width = 100) + private String menuName; + + @Column("parent_id") + @Comment("父菜单ID") + @ColDefine(type = ColType.VARCHAR, width = 64) + private String parentId; + + /** + * 父菜单名称 + */ + private String parentName; + + @Column("order_num") + @Comment("显示顺序") + private String orderNum; + + @Column("url") + @Comment("菜单URL") + private String url; + + @Column("menu_type") + @Comment("类型:0目录,1菜单,2按钮") + private String menuType; + + @Column("visible") + @Comment("菜单状态:0显示,1隐藏") + private boolean visible; + + @Column("perms") + @Comment("权限字符串") + private String perms; + + @Column + @Comment("菜单图标") + private String icon; + + @Column + @Comment("备注") + private String remark; + + + + /** + * 创建时间 + */ + @Column("create_time") + @Comment("创建时间 ") + @Prev(els = {@EL("$me.now()")}) + private Date createTime; + + /** + * 更新时间 + */ + @Column("update_time") + @Comment("更新时间 ") + @Prev(els = {@EL("$me.now()")}) + private Date updateTime; + + + /** 子菜单 */ + private List children = new ArrayList(); + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getMenuName() { + return menuName; + } + + public void setMenuName(String menuName) { + this.menuName = menuName; + } + + public String getParentId() { + return parentId; + } + + public void setParentId(String parentId) { + this.parentId = parentId; + } + + public String getParentName() { + return parentName; + } + + public void setParentName(String parentName) { + this.parentName = parentName; + } + + public String getOrderNum() { + return orderNum; + } + + public void setOrderNum(String orderNum) { + this.orderNum = orderNum; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getMenuType() { + return menuType; + } + + public void setMenuType(String menuType) { + this.menuType = menuType; + } + + public boolean isVisible() { + return visible; + } + + public void setVisible(boolean visible) { + this.visible = visible; + } + + public String getPerms() { + return perms; + } + + public void setPerms(String perms) { + this.perms = perms; + } + + public String getIcon() { + return icon; + } + + public void setIcon(String icon) { + this.icon = icon; + } + + public String getRemark() { + return remark; + } + + public void setRemark(String remark) { + this.remark = remark; + } + + + + public Date getCreateTime() { + return createTime; + } + + public void setCreateTime(Date createTime) { + this.createTime = createTime; + } + + + public Date getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(Date updateTime) { + this.updateTime = updateTime; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } + + public static List getMenuList(List list, String pid){ + List allMenu =new ArrayList(); + for(TestMenu menu:allMenu){ + menu.setId(R.UU32().toLowerCase()); + menu.setParentId(pid); + allMenu.add(menu); + if(Lang.isNotEmpty(menu.getChildren()) && menu.getChildren().size()>0){ + List tmp = getMenuList(menu.getChildren(),menu.getId()); + allMenu.addAll(tmp); + } + } + return allMenu; + } + + /** + * 子方法 + **/ + public static List getChild(String id, List allMenu) { + // 子菜单 + List childList = new ArrayList(); + for (TestMenu menu : allMenu) { + // 遍历所有节点,将父菜单id与传过来的id比较 + if (Strings.isNotBlank(menu.getParentId())) { + if (menu.getParentId().equals(id)) { + childList.add(menu); + } + } + } + // 把子菜单的子菜单再循环一遍 + for (TestMenu menu : childList) { + menu.setChildren(getChild(menu.getId(), allMenu)); + } // 递归退出条件 + if (childList.size() == 0) { + return null; + } + return childList; + } +} diff --git a/test/org/nutz/dao/test/meta/TestRole.java b/test/org/nutz/dao/test/meta/TestRole.java new file mode 100644 index 0000000000..62e79607c1 --- /dev/null +++ b/test/org/nutz/dao/test/meta/TestRole.java @@ -0,0 +1,250 @@ +package org.nutz.dao.test.meta; + +import org.nutz.dao.entity.annotation.*; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +/** + * 角色表 + * + * @author haiming + */ +@Table("test_role") +public class TestRole extends BaseBean implements Serializable { + private static final long serialVersionUID = 1L; + + @Column + @Name + @Comment("ID") + @ColDefine(type = ColType.VARCHAR, width = 32) + @Prev(els = {@EL("uuid()")}) + private String id; + + /** + * 角色名称 + */ + @Column("role_name") + @Comment("角色名称 ") + private String roleName; + + /** + * 角色权限 + */ + @Column("role_key") + @Comment("角色权限") + private String roleKey; + + /** + * 角色排序 + */ + @Column("role_sort") + @Comment("角色排序") + private String roleSort; + + /** + * 数据范围(1:所有数据权限;2:自定义数据权限) + */ + @Column("data_scope") + @Comment("数据范围 ") + private String dataScope; + + /** + * 角色状态(0正常 1停用) + */ + @Column("status") + @Comment("角色状态(0正常 1停用) ") + private boolean status; + + /** + * 删除标志(0代表存在 1代表删除) + */ + @Column("del_flag") + @Comment("删除标记") + @ColDefine(type = ColType.BOOLEAN) + private boolean delFlag; + + /** + * 用户是否存在此角色标识 默认不存在 + */ + private boolean flag = false; + + @Column + @Comment("备注") + private String remark; + + /** + * 菜单组 + */ + private String menuIds; + + /** + * 部门组(数据权限) + */ + private String[] deptIds; + + @ManyMany(from = "role_id", relation = "test_role_menu", to = "menu_id") + protected List menus; + + + /** + * 创建时间 + */ + @Column("create_time") + @Comment("创建时间 ") + @Prev(els = {@EL("$me.now()")}) + private Date createTime; + + /** + * 更新时间 + */ + @Column("update_time") + @Comment("更新时间 ") + @Prev(els = {@EL("$me.now()")}) + private Date updateTime; + + @Override + public boolean equals(Object obj) { + if (obj instanceof TestRole) { + TestRole role = (TestRole) obj; +// System.out.println("equal"+ role.id); + return (id.equals(role.id)); + } + return super.equals(obj); + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getRoleName() { + return roleName; + } + + public void setRoleName(String roleName) { + this.roleName = roleName; + } + + public String getRoleKey() { + return roleKey; + } + + public void setRoleKey(String roleKey) { + this.roleKey = roleKey; + } + + public String getRoleSort() { + return roleSort; + } + + public void setRoleSort(String roleSort) { + this.roleSort = roleSort; + } + + public String getDataScope() { + return dataScope; + } + + public void setDataScope(String dataScope) { + this.dataScope = dataScope; + } + + public boolean isStatus() { + return status; + } + + public void setStatus(boolean status) { + this.status = status; + } + + public boolean isDelFlag() { + return delFlag; + } + + public void setDelFlag(boolean delFlag) { + this.delFlag = delFlag; + } + + public boolean isFlag() { + return flag; + } + + public void setFlag(boolean flag) { + this.flag = flag; + } + + public String getRemark() { + return remark; + } + + public void setRemark(String remark) { + this.remark = remark; + } + + public String getMenuIds() { + return menuIds; + } + + public void setMenuIds(String menuIds) { + this.menuIds = menuIds; + } + + public String[] getDeptIds() { + return deptIds; + } + + public void setDeptIds(String[] deptIds) { + this.deptIds = deptIds; + } + + public List getMenus() { + return menus; + } + + public void setMenus(List menus) { + this.menus = menus; + } + + + public Date getCreateTime() { + return createTime; + } + + public void setCreateTime(Date createTime) { + this.createTime = createTime; + } + + public Date getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(Date updateTime) { + this.updateTime = updateTime; + } + + @Override + public String toString() { + return "TestRole{" + + "id='" + id + '\'' + + ", roleName='" + roleName + '\'' + + ", roleKey='" + roleKey + '\'' + + ", roleSort='" + roleSort + '\'' + + ", dataScope='" + dataScope + '\'' + + ", status=" + status + + ", delFlag=" + delFlag + + ", flag=" + flag + + ", remark='" + remark + '\'' + + ", menuIds='" + menuIds + '\'' + + ", deptIds=" + Arrays.toString(deptIds) + + ", menus=" + menus + + ", createTime=" + createTime + + ", updateTime=" + updateTime + + '}'; + } +} diff --git a/test/org/nutz/dao/test/meta/TestUser.java b/test/org/nutz/dao/test/meta/TestUser.java new file mode 100644 index 0000000000..5c9c89cdbd --- /dev/null +++ b/test/org/nutz/dao/test/meta/TestUser.java @@ -0,0 +1,351 @@ +package org.nutz.dao.test.meta; + +import org.nutz.dao.entity.annotation.*; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +/** + * 用户信息 + * @author haiming + */ +@Table("test_user") +public class TestUser extends BaseBean implements Serializable { + private static final long serialVersionUID = 1L; + + @Name + @Column + @Comment("ID") + @ColDefine(type = ColType.VARCHAR, width = 32) + private String id; + + /** + * 部门ID + */ + @Column("dept_id") + @Comment("部门ID") + private String deptId; + + /** + * 部门父ID + */ + private String parentId; + + /** + * 登录名称 + */ + @Column("login_name") + @Comment("登录名称") + private String loginName; + + /** + * 用户名称 + */ + @Column("user_name") + @Comment("用户名称") + private String userName; + + /** + * 密码 + */ + @Column("password") + @Comment("密码") + private String password; + + /** + * 盐加密 + */ + @Column("salt") + @Comment("盐加密") + private String salt; + + /** + * 用户邮箱 + */ + @Column("email") + @Comment("用户邮箱") + private String email; + + /** + * 手机号码 + */ + @Column("phonenumber") + @Comment("手机号码") + private String phonenumber; + + /** + * 用户性别 + */ + @Column + @Comment("用户性别") + private String gender; + + /** + * 用户头像 + */ + @Column + @Comment("用户头像") + private String avatar; + + @One(field = "avatar") + private TestImage image; + + /** + * 帐号状态(0正常 1停用) + */ + @Column + @Comment("帐号状态(0正常 1停用) ") + private boolean status; + + /** + * 删除标志(0代表存在 1代表删除) + */ + @Column("del_flag") + @Comment("删除标记") + @ColDefine(type = ColType.BOOLEAN) + private boolean delFlag; + + /** + * 最后登陆IP + */ + @Column("login_ip") + @Comment("最后登陆IP") + private String loginIp; + + /** + * 最后登陆时间 + */ + @Column("login_date") + @Comment("最后登陆时间") + private Date loginDate; + + /** + * 角色集合 + */ + @ManyMany(from = "user_id", relation = "test_user_role", to = "role_id") + private List roles; + + /** + * 角色组 + */ + private String roleIds; + + /** + * 岗位组 + */ + private String postIds; + + + /** + * 创建时间 + */ + @Column("create_time") + @Comment("创建时间 ") + private Date createTime; + + + + /** + * 更新时间 + */ + @Column("update_time") + @Comment("更新时间 ") + private Date updateTime; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getDeptId() { + return deptId; + } + + public void setDeptId(String deptId) { + this.deptId = deptId; + } + + public String getParentId() { + return parentId; + } + + public void setParentId(String parentId) { + this.parentId = parentId; + } + + public String getLoginName() { + return loginName; + } + + public void setLoginName(String loginName) { + this.loginName = loginName; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getSalt() { + return salt; + } + + public void setSalt(String salt) { + this.salt = salt; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPhonenumber() { + return phonenumber; + } + + public void setPhonenumber(String phonenumber) { + this.phonenumber = phonenumber; + } + + public String getGender() { + return gender; + } + + public void setGender(String gender) { + this.gender = gender; + } + + public String getAvatar() { + return avatar; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public TestImage getImage() { + return image; + } + + public void setImage(TestImage image) { + this.image = image; + } + + public boolean isStatus() { + return status; + } + + public void setStatus(boolean status) { + this.status = status; + } + + public boolean isDelFlag() { + return delFlag; + } + + public void setDelFlag(boolean delFlag) { + this.delFlag = delFlag; + } + + public String getLoginIp() { + return loginIp; + } + + public void setLoginIp(String loginIp) { + this.loginIp = loginIp; + } + + public Date getLoginDate() { + return loginDate; + } + + public void setLoginDate(Date loginDate) { + this.loginDate = loginDate; + } + + public List getRoles() { + return roles; + } + + public void setRoles(List roles) { + this.roles = roles; + } + + public String getRoleIds() { + return roleIds; + } + + public void setRoleIds(String roleIds) { + this.roleIds = roleIds; + } + + public String getPostIds() { + return postIds; + } + + public void setPostIds(String postIds) { + this.postIds = postIds; + } + + public Date getCreateTime() { + return createTime; + } + + public void setCreateTime(Date createTime) { + this.createTime = createTime; + } + + + public Date getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(Date updateTime) { + this.updateTime = updateTime; + } + + @Override + public String toString() { + return "TestUser{" + + "id='" + id + '\'' + + ", deptId='" + deptId + '\'' + + ", parentId='" + parentId + '\'' + + ", loginName='" + loginName + '\'' + + ", userName='" + userName + '\'' + + ", password='" + password + '\'' + + ", salt='" + salt + '\'' + + ", email='" + email + '\'' + + ", phonenumber='" + phonenumber + '\'' + + ", gender='" + gender + '\'' + + ", avatar='" + avatar + '\'' + + ", image=" + image + + ", status=" + status + + ", delFlag=" + delFlag + + ", loginIp='" + loginIp + '\'' + + ", loginDate=" + loginDate + + ", roles=" + roles + + ", roleIds='" + roleIds + '\'' + + ", postIds='" + postIds + '\'' + + ", createTime=" + createTime + + ", updateTime=" + updateTime + + '}'; + } +} diff --git a/test/org/nutz/dao/test/meta/XPet.java b/test/org/nutz/dao/test/meta/XPet.java index 453c701cc9..f043d6aef5 100644 --- a/test/org/nutz/dao/test/meta/XPet.java +++ b/test/org/nutz/dao/test/meta/XPet.java @@ -2,14 +2,26 @@ import java.sql.Timestamp; +import org.nutz.dao.entity.annotation.EL; +import org.nutz.dao.entity.annotation.Id; +import org.nutz.dao.entity.annotation.Name; import org.nutz.dao.entity.annotation.Table; +import org.nutz.dao.interceptor.annotation.PrevInsert; +import org.nutz.dao.interceptor.annotation.PrevUpdate; @Table("t_xpet") public class XPet { + @Id private long id; + @Name + @PrevInsert(uu32 = true, nullEffective = true) private String name; + + @PrevInsert(now=true) private Timestamp createTime; + @PrevUpdate(els=@EL("now()")) + @PrevInsert(now=true) private Timestamp updateTime; private Timestamp otherTime; public long getId() { diff --git a/test/org/nutz/dao/test/meta/issueXXX/IotObject.java b/test/org/nutz/dao/test/meta/issueXXX/IotObject.java new file mode 100644 index 0000000000..093cb3ad09 --- /dev/null +++ b/test/org/nutz/dao/test/meta/issueXXX/IotObject.java @@ -0,0 +1,32 @@ +package org.nutz.dao.test.meta.issueXXX; + +import org.nutz.dao.entity.annotation.ColDefine; +import org.nutz.dao.entity.annotation.ColType; +import org.nutz.dao.entity.annotation.Id; +import org.nutz.dao.entity.annotation.Table; + +@Table("t_iot_object") +public class IotObject { + + @Id + private int id; + + @ColDefine(type=ColType.INT) + protected IotProductStatus stat; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public IotProductStatus getStat() { + return stat; + } + + public void setStat(IotProductStatus stat) { + this.stat = stat; + } +} diff --git a/test/org/nutz/dao/test/meta/issueXXX/IotProductStatus.java b/test/org/nutz/dao/test/meta/issueXXX/IotProductStatus.java new file mode 100644 index 0000000000..f0353ed7e5 --- /dev/null +++ b/test/org/nutz/dao/test/meta/issueXXX/IotProductStatus.java @@ -0,0 +1,38 @@ +package org.nutz.dao.test.meta.issueXXX; +public enum IotProductStatus { + DEVELOP(100, "开发"), + PRODUCT(200, "生产"); + + private final int value; + private final String text; + + IotProductStatus(int value, String text) { + this.value = value; + this.text = text; + } + + public int getValue() { + return value; + } + + public String getText() { + return text; + } + + public int value() { + return value; + } + + public String text() { + return text; + } + + public static IotProductStatus from(int value) { + for (IotProductStatus t : values()) { + if (t.value == value) { + return t; + } + } + throw new IllegalArgumentException("unknown IotProductStatus: " + value); + } +} diff --git a/test/org/nutz/dao/test/meta/issue_ix3il/IssueIX3IL.java b/test/org/nutz/dao/test/meta/issue_ix3il/IssueIX3IL.java new file mode 100644 index 0000000000..4122efe2ec --- /dev/null +++ b/test/org/nutz/dao/test/meta/issue_ix3il/IssueIX3IL.java @@ -0,0 +1,24 @@ +package org.nutz.dao.test.meta.issue_ix3il; + +import static org.junit.Assert.assertEquals; + +import java.util.List; + +import org.junit.Test; +import org.nutz.dao.test.DaoCase; +import org.nutz.json.Json; +import org.nutz.lang.Streams; + +public class IssueIX3IL extends DaoCase { + + @Test + public void test_ix3il() { + dao.create(MidFunctionRole.class, true); + + List list = Json.fromJsonAsList(MidFunctionRole.class, Streams.utf8r(getClass().getResourceAsStream("test_data.json"))); + assertEquals(12, list.size()); + dao.insert(list); + + assertEquals(12, dao.count(MidFunctionRole.class)); + } +} diff --git a/test/org/nutz/dao/test/meta/issue_ix3il/MidFunctionRole.java b/test/org/nutz/dao/test/meta/issue_ix3il/MidFunctionRole.java new file mode 100644 index 0000000000..c8c3d9cb43 --- /dev/null +++ b/test/org/nutz/dao/test/meta/issue_ix3il/MidFunctionRole.java @@ -0,0 +1,44 @@ +package org.nutz.dao.test.meta.issue_ix3il; + +import org.nutz.dao.entity.annotation.PK; +import org.nutz.dao.entity.annotation.Table; + +@Table("mid_function_role") +@PK({ "modelId", "roleId" }) +public class MidFunctionRole { + /** + * 模型ID + */ + private Long modelId; + /** + * 角色ID + */ + private Long roleId; + + public MidFunctionRole() { + super(); + } + + public MidFunctionRole(Long modelId, Long roleId) { + super(); + this.modelId = modelId; + this.roleId = roleId; + } + + public Long getModelId() { + return modelId; + } + + public void setModelId(Long modelId) { + this.modelId = modelId; + } + + public Long getRoleId() { + return roleId; + } + + public void setRoleId(Long roleId) { + this.roleId = roleId; + } + +} diff --git a/test/org/nutz/dao/test/meta/issue_ix3il/test_data.json b/test/org/nutz/dao/test/meta/issue_ix3il/test_data.json new file mode 100644 index 0000000000..2102086217 --- /dev/null +++ b/test/org/nutz/dao/test/meta/issue_ix3il/test_data.json @@ -0,0 +1,37 @@ +[{ + "modelId": 100410, + "roleId": 10002 +}, { + "modelId": 1100, + "roleId": 10002 +}, { + "modelId": 100910, + "roleId": 10002 +}, { + "modelId": 1000, + "roleId": 10002 +}, { + "modelId": 1200, + "roleId": 10002 +}, { + "modelId": 100610, + "roleId": 10002 +}, { + "modelId": 100400, + "roleId": 10002 +}, { + "modelId": 100710, + "roleId": 10002 +}, { + "modelId": 100810, + "roleId": 10002 +}, { + "modelId": 1000007, + "roleId": 10002 +}, { + "modelId": 100510, + "roleId": 10002 +}, { + "modelId": 1300, + "roleId": 10002 +}] \ No newline at end of file diff --git a/test/org/nutz/dao/test/meta/nutzcn/AbcPet.java b/test/org/nutz/dao/test/meta/nutzcn/AbcPet.java new file mode 100644 index 0000000000..f68b3bbdb8 --- /dev/null +++ b/test/org/nutz/dao/test/meta/nutzcn/AbcPet.java @@ -0,0 +1,51 @@ +package org.nutz.dao.test.meta.nutzcn; + +import org.nutz.dao.entity.annotation.Id; +import org.nutz.dao.entity.annotation.One; +import org.nutz.dao.entity.annotation.Table; + +@Table("t_abc_pet") +public class AbcPet { + + @Id + private long id; + + private String name; + + private long userId; + + @One + private AbcUser user; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public long getUserId() { + return userId; + } + + public void setUserId(long userId) { + this.userId = userId; + } + + public AbcUser getUser() { + return user; + } + + public void setUser(AbcUser user) { + this.user = user; + } +} \ No newline at end of file diff --git a/test/org/nutz/dao/test/meta/nutzcn/AbcUser.java b/test/org/nutz/dao/test/meta/nutzcn/AbcUser.java new file mode 100644 index 0000000000..8598aa8c73 --- /dev/null +++ b/test/org/nutz/dao/test/meta/nutzcn/AbcUser.java @@ -0,0 +1,54 @@ +package org.nutz.dao.test.meta.nutzcn; + +import org.nutz.dao.entity.annotation.Id; +import org.nutz.dao.entity.annotation.Name; +import org.nutz.dao.entity.annotation.One; +import org.nutz.dao.entity.annotation.Table; + +@Table("t_abc_user") +public class AbcUser { + @Id + private long id; + + @Name + private String name; + + private int age; + + @One(key="userId", field="id") + private AbcPet pet; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + public AbcPet getPet() { + return pet; + } + + public void setPet(AbcPet pet) { + this.pet = pet; + } + + +} \ No newline at end of file diff --git a/test/org/nutz/dao/test/normal/QueryTest.java b/test/org/nutz/dao/test/normal/QueryTest.java index c2b59199ec..ee93cfad1f 100644 --- a/test/org/nutz/dao/test/normal/QueryTest.java +++ b/test/org/nutz/dao/test/normal/QueryTest.java @@ -278,7 +278,7 @@ public void fetchLinks_with_cnd() { dao.create(Master.class, true); Master master = new Master(); master.setName("zozoh"); - + Pet petA = new Pet(); petA.setName("wendal"); petA.setAge(31); @@ -287,9 +287,31 @@ public void fetchLinks_with_cnd() { petB.setAge(30); master.setPets(Arrays.asList(petA, petB)); dao.insertWith(master, null); - + master = dao.fetch(Master.class, master.getName()); dao.fetchLinks(master, null, Cnd.where("age", "=", 31)); assertEquals(1, master.getPets().size()); } + + @Test + public void queryByJoin_with_cnd() { + dao.create(Pet.class, true); + dao.create(Master.class, true); + Master master = new Master(); + master.setName("zozoh"); + + Pet petA = new Pet(); + petA.setName("wendal"); + petA.setAge(31); + Pet petB = new Pet(); + petB.setName("pangwu"); + petB.setAge(30); + master.setPets(Arrays.asList(petA, petB)); + dao.insertWith(master, null); + Cnd cnd = Cnd.NEW(); + cnd.asc("name"); + List list = dao.queryByJoin(Master.class, "pets",cnd); + assertEquals(1,list.size()); + assertEquals(2,list.get(0).getPets().size()); + } } diff --git a/test/org/nutz/dao/test/normal/SimpleDaoTest.java b/test/org/nutz/dao/test/normal/SimpleDaoTest.java index a439baf585..4294a46d5a 100644 --- a/test/org/nutz/dao/test/normal/SimpleDaoTest.java +++ b/test/org/nutz/dao/test/normal/SimpleDaoTest.java @@ -7,16 +7,24 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; import java.lang.reflect.Method; import java.math.BigDecimal; import java.sql.Connection; import java.sql.PreparedStatement; +import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.TreeMap; @@ -38,11 +46,15 @@ import org.nutz.dao.entity.Entity; import org.nutz.dao.entity.MappingField; import org.nutz.dao.entity.Record; +import org.nutz.dao.entity.annotation.Table; import org.nutz.dao.impl.DaoExecutor; import org.nutz.dao.impl.NutDao; +import org.nutz.dao.impl.NutTxDao; import org.nutz.dao.impl.SimpleDataSource; import org.nutz.dao.impl.sql.NutStatement; import org.nutz.dao.jdbc.JdbcExpert; +import org.nutz.dao.jdbc.Jdbcs; +import org.nutz.dao.jdbc.ValueAdaptor; import org.nutz.dao.pager.Pager; import org.nutz.dao.sql.Criteria; import org.nutz.dao.sql.DaoStatement; @@ -58,6 +70,7 @@ import org.nutz.dao.test.meta.Pet; import org.nutz.dao.test.meta.PetObj; import org.nutz.dao.test.meta.Platoon; +import org.nutz.dao.test.meta.PojoWithInteger; import org.nutz.dao.test.meta.PojoWithNull; import org.nutz.dao.test.meta.SimplePOJO; import org.nutz.dao.test.meta.Soldier; @@ -82,14 +95,21 @@ import org.nutz.dao.test.meta.issue901.XPlace; import org.nutz.dao.test.meta.issue918.Region; import org.nutz.dao.test.meta.issue928.BeanWithSet; +import org.nutz.dao.test.meta.issueXXX.IotObject; +import org.nutz.dao.test.meta.issueXXX.IotProductStatus; +import org.nutz.dao.test.meta.nutzcn.AbcPet; +import org.nutz.dao.test.meta.nutzcn.AbcUser; import org.nutz.dao.util.Daos; import org.nutz.dao.util.blob.SimpleBlob; import org.nutz.dao.util.blob.SimpleClob; import org.nutz.dao.util.cri.SimpleCriteria; +import org.nutz.dao.util.meta.SystemUser; +import org.nutz.dao.util.tables.TablesFilter; import org.nutz.json.Json; import org.nutz.lang.Files; import org.nutz.lang.Lang; import org.nutz.lang.Stopwatch; +import org.nutz.lang.Streams; import org.nutz.lang.random.R; import org.nutz.lang.util.NutMap; import org.nutz.trans.Atom; @@ -110,6 +130,10 @@ private void insertRecords(int len) { } } + + + + /** * for issue #675 提供一个直接返回对象的方法 */ @@ -451,8 +475,9 @@ public void test_insert_with_id() { assertEquals(9090, pet.getId()); } - @Test - public void test_use_blob_clob() { + @SuppressWarnings("resource") + @Test + public void test_use_blob_clob() throws FileNotFoundException, IOException, SQLException { dao.create(UseBlobClob.class, true); UseBlobClob use = new UseBlobClob(); use.setName("wendal"); @@ -463,6 +488,12 @@ public void test_use_blob_clob() { use.setX(new SimpleBlob(Files.findFile("log4j.properties"))); use.setY(new SimpleClob(Files.findFile("log4j.properties"))); dao.update(use); + + use = dao.fetch(UseBlobClob.class, "wendal"); + assertNotNull(use.getX()); + assertNotNull(use.getY()); + + assertTrue(Streams.equals(use.getX().getBinaryStream(), new FileInputStream(Files.findFile("log4j.properties")))); } @Test @@ -845,13 +876,13 @@ public void test_coldefine_len() { user.setSalt(R.UU32()); user.setPassword(Lang.sha1("abc" + user.getSalt())); dao.insert(user); - + NutMap map = new NutMap(".table", "t_test_user"); map.put("+*id", 0); map.put("name", "wendal"); dao.insert(map); assertNotNull(map.get("id")); - + map = new NutMap(".table", "t_test_user"); map.put("*+id", 0); map.put("name", "wendal2"); @@ -873,7 +904,7 @@ public void test_issue_1179() { public void test_issue_1235() { dao.create(Pet.class, false); dao.insert(Pet.create(R.UU32())); - List list = dao.query("t_pet", null, null, "id,name"); + List list = dao.query("t_pet", Cnd.where("age", ">", 0), null, "id,name"); assertNotNull(list); assertTrue(list.size() > 0); assertEquals(2, list.get(0).size()); @@ -912,9 +943,12 @@ public void test_fetch_by_join() { platoon.setLeader(soldier); platoon1.setLeader(soldier1); platoon2.setLeader(soldier2); - dao.insertWith(platoon, null); - dao.insertWith(platoon1, null); - dao.insertWith(platoon2, null); + platoon.setLeader2(soldier); + platoon1.setLeader2(soldier1); + platoon2.setLeader2(soldier2); + dao.insertWith(platoon, "^(base|leader)$"); + dao.insertWith(platoon1, "^(base|leader)$"); + dao.insertWith(platoon2, "^(base|leader)$"); // ======================================= // 用条件查 @@ -926,6 +960,7 @@ public void test_fetch_by_join() { assertEquals("wendal", platoon.getName()); assertNotNull(platoon.getLeader()); + System.out.println(Json.toJson(platoon.getLeader())); assertEquals("stone", platoon.getLeader().getName()); assertNotNull(platoon.getBase()); @@ -961,6 +996,8 @@ public void test_fetch_by_join() { // @One分页测试,总共3个,分页的为2个 assertEquals(3,dao.queryByJoin(Platoon.class, null, null).size()); assertEquals(2,dao.queryByJoin(Platoon.class, null, null,new Pager(1, 2)).size()); + + assertEquals(3,dao.countByJoin(Platoon.class, null, null)); } @Test @@ -989,9 +1026,9 @@ public void test_migration_issue1254() { } finally { Daos.FORCE_WRAP_COLUMN_NAME = false; } - + } - + @Test public void test_fast_insert_maps() { List list = new ArrayList(); @@ -1001,10 +1038,10 @@ public void test_fast_insert_maps() { list.add(pet); } list.get(0).setv(".table", "t_pet"); - + dao.fastInsert(list); } - + @Test public void test_issue_1284() { dao.create(Issue1284.class, true); @@ -1014,7 +1051,7 @@ public void test_issue_1284() { bean.setAge(20); dao.insert(bean); } - + @Test public void test_issue_insert_or_update() { try { @@ -1029,7 +1066,7 @@ public void test_issue_insert_or_update() { e.printStackTrace(); throw e; } - + try { dao.create(DumpData.class, true); DumpData dump = new DumpData(); @@ -1044,7 +1081,7 @@ public void test_issue_insert_or_update() { // TODO: handle exception } } - + @Test public void test_issue_1302() { dao.create(Issue1302Master.class, false); @@ -1056,7 +1093,7 @@ public void test_issue_1302() { pojo = dao.fetch(Issue1302Master.class, pojo.getName()); assertEquals(Issue1302UserAction.VIEW, pojo.getAct()); } - + @Test public void test_truncate() { @@ -1064,20 +1101,20 @@ public void test_truncate() { dao.create(Pet.class, false); dao.insert(Pet.create(10)); assertTrue(dao.count(Pet.class) > 0); - + // 干掉 dao.truncate(Pet.class); assertTrue(dao.count(Pet.class) == 0); - + // 再插入10条记录 dao.insert(Pet.create(10)); assertTrue(dao.count(Pet.class) > 0); - + //再干掉 dao.truncate(dao.getEntity(Pet.class).getTableName()); assertTrue(dao.count(Pet.class) == 0); } - + @Test public void test_issue1342() { if (!dao.meta().isMySql()) @@ -1090,7 +1127,7 @@ public void test_issue1342() { + "PARTITION p_catchall VALUES LESS THAN MAXVALUE)")); dao.query("t_issue_1342", new SimpleCriteria("partition(p_2017)")); } - + @Test public void test_pk_version() { dao.create(IssuePkVersion.class, true); @@ -1110,13 +1147,13 @@ public void test_pk_version() { ve = dao.fetchx(IssuePkVersion.class, "abc_1", 1); assertEquals(99, ve.getPrice()); } - + @Test public void test_mysql_migration() { if (!dao.meta().isMySql()) return; dao.create(TestMysqlIndex.class, true); - + System.out.println("=================================="); Daos.migration(dao, TestMysqlIndex.class, true, false, true); System.out.println("=================================="); @@ -1125,4 +1162,240 @@ public void test_mysql_migration() { Daos.migration(dao, TestMysqlIndex.class, true, false, true); System.out.println("=================================="); } + + @Test + public void test_insert_chain_with_adaptor() { + dao.create(Pet.class, true); + dao.insert("t_pet", Chain.make("name", "wendal").adaptor(Jdbcs.Adaptor.asString)); + } + + @Test + public void test_nutz_tx_dao() throws Throwable { + for (int i = 0; i < 1000; i++) { + NutTxDao tx = new NutTxDao(dao); + try { + tx.beginRC(); + tx.query(Pet.class, null); + tx.commit(); + } catch (Throwable e) { + tx.rollback(); + throw e; + } + finally { + tx.close(); + } + } + } + + /** + * 按包自动创建表 + */ + @Test + public void test_dao_createTablesInPackage() { + if(dao.exists(SystemUser.class)){ + //存在则删除 + dao.drop(SystemUser.class); + } + final Set> filters=new HashSet>(); + filters.add(SystemUser.class); + Daos.createTablesInPackage(dao, SystemUser.class, true, new TablesFilter() { + @Override + public boolean match(Class klass, Table table) { + if (filters.contains(klass)) { + return false; + } else { + return true; + } + } + }); + //此表应该不存在 + assertTrue(!dao.exists(SystemUser.class)); + Daos.createTablesInPackage(dao,SystemUser.class,true); + //此表存在 + assertTrue(dao.exists(SystemUser.class)); + } + + @Test + public void test_cnd_clone() throws IOException { + try { + Cnd cnd = Cnd.NEW().and("abc", "=", 123); + Cnd.byCri(cnd.getCri()); + Cnd.byCri(cnd.getCri()); + } + catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + @Test + public void test_cnd_clone2() { + // 序列化的方式, 不要考究SQL条件的合理性 + dao.insert(Pet.create(30)); + Cnd cnd = Cnd.where("age", ">", 15).and(Cnd.exps("age", ">", 0).and("age", "<", 16)); + cnd.asc("age"); + int t = dao.count(Pet.class, cnd); + assertNotNull(Lang.toBytes(cnd)); + Stopwatch sw = Stopwatch.begin(); + cnd.clone(); + sw.stop(); + System.out.println(sw); + assertEquals(t, dao.count(Pet.class, cnd.clone())); + + // 仅拷贝where条件 + Cnd cndCloned = cnd.cloneWhere(); + assertEquals(t, dao.count(Pet.class, cndCloned)); + + // 修改原来的cnd条件, 使其互相矛盾,结果肯定是0 + cnd.and("age", "<", 0); + assertEquals(0, dao.count(Pet.class, cnd)); + + // 克隆的cndCloned应该不受影响 + assertEquals(t, dao.count(Pet.class, cndCloned)); + } + + @Test + public void test_issue_1294() { + dao.clear(Pet.class); + dao.insert(Pet.create("wendal")); + Record re = new Record(); + re.put(".table", "t_pet"); + re.put("*name", "wendal"); + re.put("age", 30); + dao.update(re, Cnd.where("age", ">", -100)); + assertEquals(30, dao.fetch(Pet.class).getAge()); + + re = new Record(); + re.put(".table", "t_pet"); + re.put("age", 31); + dao.update(re, Cnd.where("age", ">", -100)); + assertEquals(31, dao.fetch(Pet.class).getAge()); + } + + // 这个TestCase的意义何在? 删掉了 + //@Test + public void test_issue_xxx() { + final Object[] re = new Object[1]; + ValueAdaptor va = new ValueAdaptor() { + + @Override + public void set(PreparedStatement stat, Object obj, int index) throws SQLException { + re[0] = obj; + stat.setString(index, "ABC"); + } + + @Override + public Object get(ResultSet rs, String colName) throws SQLException { + // TODO Auto-generated method stub + return null; + } + }; + List name = Arrays.asList("wendal"); + Sql sql = Sqls.create("select * from t_pet where name=@name"); + sql.setParam("name", name); + sql.setValueAdaptor("name", va); + dao.execute(sql); + assertEquals(name, re[0]); + } + + @Test + public void test_update_integer() { + dao.create(PojoWithInteger.class, true); + PojoWithInteger pojo = new PojoWithInteger(); + pojo.setName("wendal"); + pojo.setAge(20); + pojo.setT(12); + dao.insert(pojo); + + pojo.setT(null); + pojo.setAge(30); + dao.updateIgnoreNull(pojo); + pojo = dao.fetch(PojoWithInteger.class, pojo.getName()); + assertEquals(30, pojo.getAge()); + assertEquals(12, pojo.getT().intValue()); + + + pojo.setT(0); + pojo.setAge(31); + dao.updateIgnoreNull(pojo); + pojo = dao.fetch(PojoWithInteger.class, pojo.getName()); + assertEquals(31, pojo.getAge()); + assertEquals(0, pojo.getT().intValue()); + } + + @Test + public void test_issue_1425() { + List maps = new LinkedList(); + // 第一个对象只有name和alias + NutMap map = new NutMap(); + map.put("name", "wendal"); + map.put("alias", "XXX"); + maps.add(map); + // 第二个对象只有name和age + map = new NutMap(); + map.put("name", "zozoh"); + map.put("age", 30); + maps.add(map); + dao.create(Pet.class, true); + + // 设置表名 + maps.get(0).put(".table", "t_pet"); + // 应该会插入name, alias, age 三个字段 + dao.fastInsert(maps, true); + + // 按上述插入 + // --> wendal的alias应该存在, age不存在 + Pet wendal = dao.fetch(Pet.class, "wendal"); + assertEquals("XXX", wendal.getNickName()); + assertEquals(0, wendal.getAge()); + + // --> wendal的alias应该不存在, age存在 + Pet zozoh = dao.fetch(Pet.class, "zozoh"); + assertEquals(null, zozoh.getNickName()); + assertEquals(30, zozoh.getAge()); + } + + @Test + public void test_wizzer() { + dao.create(IotObject.class, true); + + IotObject a = new IotObject(); + a.setStat(IotProductStatus.DEVELOP); + dao.insert(a); + a = dao.fetch(IotObject.class, a.getId()); + assertNotNull(a); + assertEquals(IotProductStatus.DEVELOP, a.getStat()); + System.out.println(a.getStat().value()); + for (IotProductStatus stat : IotProductStatus.values()) { + System.out.println("-->"+stat.value()); + } + dao.setupProperties(new NutMap()); + } + + @Test + public void test_queryByJoin_2() { + dao.create(AbcUser.class, true); + dao.create(AbcPet.class, true); + Map cnds = new HashMap(); + cnds.put("pet", Cnd.where("id", ">", 0)); + dao.queryByJoin(AbcUser.class, null, null, null, cnds); + } + + @Test + public void test_create_table_by_map() { + String tableName = "t_from_map"; + dao.drop(tableName); + NutMap map = new NutMap(); + map.put("*+id", 0); + map.put("*name", ""); + map.put("age", 0); + // 指定表名称 + dao.create(tableName, map, true); + // 通过.table指定 + map.put(".table", tableName); + dao.create(map, true); + dao.insert(new NutMap(".table", tableName).setv("name", "wendal").setv("age", 18)); + Record re = dao.fetch(tableName, Cnd.where("name", "=", "wendal")); + assertEquals(18, re.getInt("age")); + } } diff --git a/test/org/nutz/dao/test/normal/SupportedFieldTypeTest.java b/test/org/nutz/dao/test/normal/SupportedFieldTypeTest.java index 670876e6ce..d05e59332f 100644 --- a/test/org/nutz/dao/test/normal/SupportedFieldTypeTest.java +++ b/test/org/nutz/dao/test/normal/SupportedFieldTypeTest.java @@ -1,219 +1,248 @@ -package org.nutz.dao.test.normal; - -import static org.junit.Assert.*; - -import java.lang.reflect.Field; -import java.sql.Date; -import java.sql.Time; -import java.sql.Timestamp; - -import org.junit.Test; - -import org.nutz.castor.Castors; -import org.nutz.castor.FailToCastObjectException; -import org.nutz.dao.Chain; -import org.nutz.dao.entity.annotation.*; -import org.nutz.dao.test.DaoCase; -import org.nutz.lang.Lang; -import org.nutz.lang.Mirror; -import org.nutz.lang.meta.Email; - -public class SupportedFieldTypeTest extends DaoCase { - - @Override - protected void before() { - dao.create(EntityTypes.class, true); - } - - public static enum TT { - A, B - } - - @Table("dao_supported_type") - public static class EntityTypes { - @Column - @Id - public int id; - - @Column - @Name - public String name; - - @Column - @Default("zozoh@gmail.com") - public Email email; - - @Column - public TT enum_s; - - // It will auto detect db, and use INT - @Column - public TT enum_i; - - @Column - public boolean bool_p; - - @Column - public Boolean bool_obj; - - @Column - public char char_p; - - @Column - public Character char_obj; - - @Column - public Date sqlDate; - - @Column - public Time sqlTime; - - @Column - public Timestamp sqlDT; - - @Column - public int int_p; - - @Column - public Integer int_obj; - - @Column - public float float_p; - - @Column - public Float float_obj; - - @Column - public short short_p; - - @Column - public Short short_obj; - - @Column - public byte byte_p; - - @Column - public Byte byte_obj; - - @Column - public long long_p; - - @Column - public Long long_obj; - - @Column - public double double_p; - - @Column - public Double double_obj; - - } - - @Test - public void insert_char_field() { - dao.insert(EntityTypes.class, Chain .make("char_p", 't') - .add("char_obj", Character.valueOf('O')) - .add("name", "ABC")); - EntityTypes et = dao.fetch(EntityTypes.class); - assertEquals('t', et.char_p); - assertEquals('O', et.char_obj.charValue()); - } - - @Test - public void insert_timestamp_field() { - Timestamp tm = new Timestamp(System.currentTimeMillis()/1000*1000); - dao.insert(EntityTypes.class, Chain.make("name", "ABC").add("sqlDT", tm)); - EntityTypes et = dao.fetch(EntityTypes.class); - if (dao.meta().isPostgresql()) - assertEquals(tm.getTime(), et.sqlDT.getTime()); - else - assertEquals(tm.getTime() / 1000, et.sqlDT.getTime() / 1000); - } - - @Test - public void check_for_sqlTime() { - Time time = Castors.me().castTo("07:09:12", Time.class); - dao.insert(EntityTypes.class, Chain.make("name", "ABC").add("sqlTime", time)); - EntityTypes et = dao.fetch(EntityTypes.class); - assertEquals(time.toString(), et.sqlTime.toString()); - } - - @Test - public void check_update_sqlTimestamp() { - EntityTypes exp = new EntityTypes(); - exp.name = "T"; - Timestamp tm = new Timestamp(System.currentTimeMillis()/1000*1000); - exp.sqlDT = tm; - dao.insert(exp); - exp = dao.fetch(EntityTypes.class, "T"); - // MySql TIMESTAMP precision only to second - assertEquals(tm.getTime() / 1000, exp.sqlDT.getTime() / 1000); - } - - @Test - public void check_if_support_all_normal_types() throws FailToCastObjectException { - String d = "2009-02-01"; - String t = "12:23:23"; - String dt = d + " " + t; - Date date = Castors.me().castTo(d, Date.class); - Time time = Castors.me().castTo(t, Time.class); - Timestamp ts = Castors.me().castTo(dt, Timestamp.class); - EntityTypes exp = new EntityTypes(); - exp.name = "XX"; - exp.enum_s = TT.B; - exp.enum_i = TT.A; - exp.char_p = 'G'; - exp.char_obj = 'O'; - exp.int_p = 23; - exp.int_obj = 23; - exp.float_p = 34.67f; - exp.float_obj = 34.68f; - exp.short_p = 6; - exp.short_obj = 6; - exp.byte_p = 2; - exp.byte_obj = 4; - exp.long_p = 56787; - exp.long_obj = 5678L; - exp.double_p = 2.4325243; - exp.double_obj = 3.4325243; - exp.sqlDate = date; - exp.sqlTime = time; - exp.sqlDT = ts; - dao.insert(exp); - EntityTypes et = dao.fetch(EntityTypes.class); - assertEquals(exp.id, et.id); - Mirror me = Mirror.me(EntityTypes.class); - for (Field f : me.getFields()) { - Object expValue; - Object ttValue; - // Mysql 5.0.18, 会去掉毫秒数 - if (f.getName().equals("sqlTime") && - (dao.meta().isMySql() || dao.meta().isHsql())) { - expValue = me.getValue(exp, f).toString(); - ttValue = me.getValue(et, f).toString(); - } - // 其他的数据库无所谓 - else { - expValue = me.getValue(exp, f); - ttValue = me.getValue(et, f); - if (null == expValue) - continue; - } - if (!expValue.equals(ttValue) && !dao.meta().isDB2()) //DB2的精度有点问题 - throw Lang.makeThrow( "'%s' expect [%s] but it was [%s]", - f.getName(), - expValue, - ttValue); - } - assertTrue(true); - } - - @Test - public void check_insert_null_timestamp_field() { - EntityTypes exp = new EntityTypes(); - exp.name = "JJ"; - dao.insert(exp); - assertTrue(true); - } - -} +package org.nutz.dao.test.normal; + +import static org.junit.Assert.*; + +import java.lang.reflect.Field; +import java.sql.Date; +import java.sql.Time; +import java.sql.Timestamp; +import java.time.LocalDate; +import java.time.LocalDateTime; + +import org.junit.Test; + +import org.nutz.castor.Castors; +import org.nutz.castor.FailToCastObjectException; +import org.nutz.dao.Chain; +import org.nutz.dao.Cnd; +import org.nutz.dao.entity.annotation.*; +import org.nutz.dao.test.DaoCase; +import org.nutz.lang.Lang; +import org.nutz.lang.Mirror; +import org.nutz.lang.meta.Email; +import org.nutz.lang.random.R; + +public class SupportedFieldTypeTest extends DaoCase { + + @Override + protected void before() { + dao.create(EntityTypes.class, true); + } + + public static enum TT { + A, B + } + + @Table("dao_supported_type") + public static class EntityTypes { + @Column + @Id + public int id; + + @Column + @Name + public String name; + + @Column + @Default("zozoh@gmail.com") + public Email email; + + @Column + public TT enum_s; + + // It will auto detect db, and use INT + @Column + public TT enum_i; + + @Column + public boolean bool_p; + + @Column + public Boolean bool_obj; + + @Column + public char char_p; + + @Column + public Character char_obj; + + @Column + public Date sqlDate; + + @Column + public Time sqlTime; + + @Column + public Timestamp sqlDT; + + @Column + public int int_p; + + @Column + public Integer int_obj; + + @Column + public float float_p; + + @Column + public Float float_obj; + + @Column + public short short_p; + + @Column + public Short short_obj; + + @Column + public byte byte_p; + + @Column + public Byte byte_obj; + + @Column + public long long_p; + + @Column + public Long long_obj; + + @Column + public double double_p; + + @Column + public Double double_obj; + + @Column + public LocalDateTime localdt; + + @Column + public LocalDate locald; + + } + + @Test + public void insert_char_field() { + dao.insert(EntityTypes.class, Chain .make("char_p", 't') + .add("char_obj", Character.valueOf('O')) + .add("name", "ABC")); + EntityTypes et = dao.fetch(EntityTypes.class); + assertEquals('t', et.char_p); + assertEquals('O', et.char_obj.charValue()); + } + + @Test + public void insert_timestamp_field() { + Timestamp tm = new Timestamp(System.currentTimeMillis()/1000*1000); + dao.insert(EntityTypes.class, Chain.make("name", "ABC").add("sqlDT", tm)); + EntityTypes et = dao.fetch(EntityTypes.class); + if (dao.meta().isPostgresql()) + assertEquals(tm.getTime(), et.sqlDT.getTime()); + else + assertEquals(tm.getTime() / 1000, et.sqlDT.getTime() / 1000); + } + + @Test + public void check_for_sqlTime() { + Time time = Castors.me().castTo("07:09:12", Time.class); + dao.insert(EntityTypes.class, Chain.make("name", "ABC").add("sqlTime", time)); + EntityTypes et = dao.fetch(EntityTypes.class); + assertEquals(time.toString(), et.sqlTime.toString()); + } + + @Test + public void check_update_sqlTimestamp() { + EntityTypes exp = new EntityTypes(); + exp.name = "T"; + Timestamp tm = new Timestamp(System.currentTimeMillis()/1000*1000); + exp.sqlDT = tm; + dao.insert(exp); + exp = dao.fetch(EntityTypes.class, "T"); + // MySql TIMESTAMP precision only to second + assertEquals(tm.getTime() / 1000, exp.sqlDT.getTime() / 1000); + } + + @Test + public void check_if_support_all_normal_types() throws FailToCastObjectException { + String d = "2009-02-01"; + String t = "12:23:23"; + String dt = d + " " + t; + Date date = Castors.me().castTo(d, Date.class); + Time time = Castors.me().castTo(t, Time.class); + Timestamp ts = Castors.me().castTo(dt, Timestamp.class); + EntityTypes exp = new EntityTypes(); + exp.name = "XX"; + exp.enum_s = TT.B; + exp.enum_i = TT.A; + exp.char_p = 'G'; + exp.char_obj = 'O'; + exp.int_p = 23; + exp.int_obj = 23; + exp.float_p = 34.67f; + exp.float_obj = 34.68f; + exp.short_p = 6; + exp.short_obj = 6; + exp.byte_p = 2; + exp.byte_obj = 4; + exp.long_p = 56787; + exp.long_obj = 5678L; + exp.double_p = 2.4325243; + exp.double_obj = 3.4325243; + exp.sqlDate = date; + exp.sqlTime = time; + exp.sqlDT = ts; + dao.insert(exp); + EntityTypes et = dao.fetch(EntityTypes.class); + assertEquals(exp.id, et.id); + Mirror me = Mirror.me(EntityTypes.class); + for (Field f : me.getFields()) { + Object expValue; + Object ttValue; + // Mysql 5.0.18, 会去掉毫秒数 + if (f.getName().equals("sqlTime") && + (dao.meta().isMySql() || dao.meta().isHsql())) { + expValue = me.getValue(exp, f).toString(); + ttValue = me.getValue(et, f).toString(); + } + // 其他的数据库无所谓 + else { + expValue = me.getValue(exp, f); + ttValue = me.getValue(et, f); + if (null == expValue) + continue; + } + if (!expValue.equals(ttValue) && !dao.meta().isDB2()) //DB2的精度有点问题 + throw Lang.makeThrow( "'%s' expect [%s] but it was [%s]", + f.getName(), + expValue, + ttValue); + } + assertTrue(true); + } + + @Test + public void check_insert_null_timestamp_field() { + EntityTypes exp = new EntityTypes(); + exp.name = "JJ"; + dao.insert(exp); + assertNotNull(dao.fetch(EntityTypes.class, "JJ")); + } + + @Test + public void check_insert_local_date_time() { + EntityTypes exp = new EntityTypes(); + exp.name = R.UU32(); + exp.localdt = LocalDateTime.now(); + dao.insert(exp); + assertNotNull(dao.fetch(EntityTypes.class, exp.name)); + } + + @Test + public void check_insert_local_date() { + EntityTypes exp = new EntityTypes(); + exp.name = R.UU32(); + exp.locald = LocalDate.now(); + dao.insert(exp); + assertNotNull(dao.fetch(EntityTypes.class, exp.name)); + assertNotNull(dao.fetch(EntityTypes.class, Cnd.where("locald", "=", LocalDate.now()))); + } + +} diff --git a/test/org/nutz/dao/test/normal/mysql/AllMysqlTest.java b/test/org/nutz/dao/test/normal/mysql/AllMysqlTest.java index 19a5d6280f..c7e86f262e 100644 --- a/test/org/nutz/dao/test/normal/mysql/AllMysqlTest.java +++ b/test/org/nutz/dao/test/normal/mysql/AllMysqlTest.java @@ -4,5 +4,5 @@ import org.junit.runners.Suite; @RunWith(Suite.class) -@Suite.SuiteClasses({MysqlJsonTest.class}) +@Suite.SuiteClasses({MysqlJsonTest.class, MysqlJsonAdaptorTest.class}) public class AllMysqlTest {} diff --git a/test/org/nutz/dao/test/normal/mysql/MysqlJsonAdaptorTest.java b/test/org/nutz/dao/test/normal/mysql/MysqlJsonAdaptorTest.java new file mode 100644 index 0000000000..81bb845470 --- /dev/null +++ b/test/org/nutz/dao/test/normal/mysql/MysqlJsonAdaptorTest.java @@ -0,0 +1,48 @@ +package org.nutz.dao.test.normal.mysql; + +import static org.junit.Assert.assertEquals; + +import java.math.BigDecimal; + +import org.junit.Test; +import org.nutz.dao.Cnd; +import org.nutz.dao.test.DaoCase; +import org.nutz.json.Json; +import org.nutz.json.JsonFormat; + +public class MysqlJsonAdaptorTest extends DaoCase { + + @Override + protected void before() { + if (!dao.meta().isMySql()) { + return; + } + dao.create(MysqlJsonAdaptorTestBean.class, true); + } + + @Test + public void adapotor() { + if (!dao.meta().isMySql()) { + return; + } + + MysqlJsonAdaptorTestBean testBean = new MysqlJsonAdaptorTestBean(); + StudentResult result = new StudentResult(); + result.setPhysics(new BigDecimal("100")); + testBean.setNoneAdaptor(result); + testBean.setJsonAdaptor(result); + testBean.setJsonCompactAdaptor(result); + testBean.setJsonTidyAdaptor(result); + + int insertId = dao.insert(testBean).getId(); + + org.nutz.dao.entity.Record record = dao.fetch("t_mysql_json_adaptor_test_bean", Cnd.where("id","=",insertId)); + // mysql 在保存 json 格式字段的时候会自动格式化该字段的值 + // mariadb 的话就没问题 + assertEquals(Json.toJson(result, JsonFormat.tidy()), record.getString("noneAdaptor")); + assertEquals(Json.toJson(result, JsonFormat.tidy()), record.getString("noneAdaptor")); + assertEquals(Json.toJson(result, JsonFormat.tidy()), record.getString("jsonAdaptor")); + assertEquals(Json.toJson(result, JsonFormat.compact()), record.getString("jsonCompactAdaptor")); + assertEquals(Json.toJson(result, JsonFormat.tidy()), record.getString("jsonTidyAdaptor")); + } +} diff --git a/test/org/nutz/dao/test/normal/mysql/MysqlJsonAdaptorTestBean.java b/test/org/nutz/dao/test/normal/mysql/MysqlJsonAdaptorTestBean.java new file mode 100644 index 0000000000..da850ceaed --- /dev/null +++ b/test/org/nutz/dao/test/normal/mysql/MysqlJsonAdaptorTestBean.java @@ -0,0 +1,68 @@ +package org.nutz.dao.test.normal.mysql; + +import org.nutz.dao.entity.annotation.ColDefine; +import org.nutz.dao.entity.annotation.ColType; +import org.nutz.dao.entity.annotation.Id; +import org.nutz.dao.entity.annotation.Table; +import org.nutz.dao.impl.jdbc.mysql.MysqlJsonAdaptor; +import org.nutz.dao.impl.jdbc.mysql.MysqlJsonCompactAdaptor; +import org.nutz.dao.impl.jdbc.mysql.MysqlJsonTidyAdaptor; + +@Table("t_mysql_json_adaptor_test_bean") +public class MysqlJsonAdaptorTestBean { + + @Id + private int id; + + @ColDefine(customType = "json", type = ColType.MYSQL_JSON) + private StudentResult noneAdaptor; + + @ColDefine(customType = "json", type = ColType.MYSQL_JSON, adaptor = MysqlJsonAdaptor.class) + private StudentResult jsonAdaptor; + + @ColDefine(customType = "json", type = ColType.MYSQL_JSON, adaptor = MysqlJsonCompactAdaptor.class) + private StudentResult jsonCompactAdaptor; + + @ColDefine(customType = "json", type = ColType.MYSQL_JSON, adaptor = MysqlJsonTidyAdaptor.class) + private StudentResult jsonTidyAdaptor; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public StudentResult getNoneAdaptor() { + return noneAdaptor; + } + + public void setNoneAdaptor(StudentResult noneAdaptor) { + this.noneAdaptor = noneAdaptor; + } + + public StudentResult getJsonAdaptor() { + return jsonAdaptor; + } + + public void setJsonAdaptor(StudentResult jsonAdaptor) { + this.jsonAdaptor = jsonAdaptor; + } + + public StudentResult getJsonCompactAdaptor() { + return jsonCompactAdaptor; + } + + public void setJsonCompactAdaptor(StudentResult jsonCompactAdaptor) { + this.jsonCompactAdaptor = jsonCompactAdaptor; + } + + public StudentResult getJsonTidyAdaptor() { + return jsonTidyAdaptor; + } + + public void setJsonTidyAdaptor(StudentResult jsonTidyAdaptor) { + this.jsonTidyAdaptor = jsonTidyAdaptor; + } +} diff --git a/test/org/nutz/dao/test/normal/psql/AllPsqlTest.java b/test/org/nutz/dao/test/normal/psql/AllPsqlTest.java index cf5abf799b..6ade2115f7 100644 --- a/test/org/nutz/dao/test/normal/psql/AllPsqlTest.java +++ b/test/org/nutz/dao/test/normal/psql/AllPsqlTest.java @@ -4,5 +4,5 @@ import org.junit.runners.Suite; @RunWith(Suite.class) -@Suite.SuiteClasses({PsqlJsonTest.class, PsqlArrayTest.class}) +@Suite.SuiteClasses({PsqlJsonTest.class, PsqlArrayTest.class, PsqlJsonAdaptorTest.class}) public class AllPsqlTest {} diff --git a/test/org/nutz/dao/test/normal/psql/PsqlJsonAdaptorTest.java b/test/org/nutz/dao/test/normal/psql/PsqlJsonAdaptorTest.java new file mode 100644 index 0000000000..c3bc77afa0 --- /dev/null +++ b/test/org/nutz/dao/test/normal/psql/PsqlJsonAdaptorTest.java @@ -0,0 +1,47 @@ +package org.nutz.dao.test.normal.psql; + +import static org.junit.Assert.assertEquals; + +import java.math.BigDecimal; + +import org.junit.Test; +import org.nutz.dao.Cnd; +import org.nutz.dao.test.DaoCase; +import org.nutz.json.Json; +import org.nutz.json.JsonFormat; + +public class PsqlJsonAdaptorTest extends DaoCase { + + @Override + protected void before() { + if (!dao.meta().isPostgresql()) { + return; + } + dao.create(PsqlJsonAdaptorTestBean.class, true); + } + + @Test + public void adapotor() { + if (!dao.meta().isPostgresql()) { + return; + } + + PsqlJsonAdaptorTestBean testBean = new PsqlJsonAdaptorTestBean(); + org.nutz.dao.test.normal.psql.StudentResult result = new StudentResult(); + result.setPhysics(new BigDecimal("100")); + testBean.setNoneAdaptor(result); + testBean.setJsonAdaptor(result); + testBean.setJsonCompactAdaptor(result); + testBean.setJsonTidyAdaptor(result); + + int insertId = dao.insert(testBean).getId(); + + org.nutz.dao.entity.Record record = dao.fetch("t_psql_json_adaptor_test_bean", Cnd.where("id","=",insertId)); + // 设置成 jsonb 格式的时候会自动格式化该字段的值 + assertEquals(Json.toJson(result, JsonFormat.tidy()), record.getString("noneAdaptor")); + assertEquals(Json.toJson(result, JsonFormat.tidy()), record.getString("noneAdaptor")); + assertEquals(Json.toJson(result, JsonFormat.tidy()), record.getString("jsonAdaptor")); + assertEquals(Json.toJson(result, JsonFormat.compact()), record.getString("jsonCompactAdaptor")); + assertEquals(Json.toJson(result, JsonFormat.tidy()), record.getString("jsonTidyAdaptor")); + } +} diff --git a/test/org/nutz/dao/test/normal/psql/PsqlJsonAdaptorTestBean.java b/test/org/nutz/dao/test/normal/psql/PsqlJsonAdaptorTestBean.java new file mode 100644 index 0000000000..952dbbc4e5 --- /dev/null +++ b/test/org/nutz/dao/test/normal/psql/PsqlJsonAdaptorTestBean.java @@ -0,0 +1,68 @@ +package org.nutz.dao.test.normal.psql; + +import org.nutz.dao.entity.annotation.ColDefine; +import org.nutz.dao.entity.annotation.ColType; +import org.nutz.dao.entity.annotation.Id; +import org.nutz.dao.entity.annotation.Table; +import org.nutz.dao.impl.jdbc.psql.PsqlJsonAdaptor; +import org.nutz.dao.impl.jdbc.psql.PsqlJsonCompactAdaptor; +import org.nutz.dao.impl.jdbc.psql.PsqlJsonTidyAdaptor; + +@Table("t_psql_json_adaptor_test_bean") +public class PsqlJsonAdaptorTestBean { + + @Id + private int id; + + @ColDefine(customType = "jsonb", type = ColType.PSQL_JSON) + private StudentResult noneAdaptor; + + @ColDefine(customType = "json", type = ColType.PSQL_JSON, adaptor = PsqlJsonAdaptor.class) + private StudentResult jsonAdaptor; + + @ColDefine(customType = "json", type = ColType.PSQL_JSON, adaptor = PsqlJsonCompactAdaptor.class) + private StudentResult jsonCompactAdaptor; + + @ColDefine(customType = "json", type = ColType.PSQL_JSON, adaptor = PsqlJsonTidyAdaptor.class) + private StudentResult jsonTidyAdaptor; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public org.nutz.dao.test.normal.psql.StudentResult getNoneAdaptor() { + return noneAdaptor; + } + + public void setNoneAdaptor(StudentResult noneAdaptor) { + this.noneAdaptor = noneAdaptor; + } + + public StudentResult getJsonAdaptor() { + return jsonAdaptor; + } + + public void setJsonAdaptor(StudentResult jsonAdaptor) { + this.jsonAdaptor = jsonAdaptor; + } + + public StudentResult getJsonCompactAdaptor() { + return jsonCompactAdaptor; + } + + public void setJsonCompactAdaptor(StudentResult jsonCompactAdaptor) { + this.jsonCompactAdaptor = jsonCompactAdaptor; + } + + public StudentResult getJsonTidyAdaptor() { + return jsonTidyAdaptor; + } + + public void setJsonTidyAdaptor(StudentResult jsonTidyAdaptor) { + this.jsonTidyAdaptor = jsonTidyAdaptor; + } +} diff --git a/test/org/nutz/dao/test/sqls/CustomizedSqlsTest.java b/test/org/nutz/dao/test/sqls/CustomizedSqlsTest.java index 39553a39cb..ccf0ff6fb0 100644 --- a/test/org/nutz/dao/test/sqls/CustomizedSqlsTest.java +++ b/test/org/nutz/dao/test/sqls/CustomizedSqlsTest.java @@ -1,7 +1,5 @@ package org.nutz.dao.test.sqls; -import static org.junit.Assert.*; - import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; @@ -10,7 +8,6 @@ import java.util.List; import org.junit.Test; - import org.nutz.Nutzs; import org.nutz.dao.Cnd; import org.nutz.dao.SqlNotFoundException; @@ -33,6 +30,10 @@ import org.nutz.dao.util.cri.SqlExpression; import org.nutz.trans.Atom; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + public class CustomizedSqlsTest extends DaoCase { @Test @@ -156,7 +157,7 @@ public void test_statice_null_field() { @Test public void test_cnd_pager() { - pojos.init(); + pojos.initPet(); Sql sql = Sqls.create("select * from t_pet $condition"); sql.setCondition(Cnd.where("name", "=", "wendal")); Pager pager = dao.createPager(1, 20); @@ -167,6 +168,7 @@ public void test_cnd_pager() { @Test public void test_in() { Sqls.setSqlBorning(NutSql.class); + pojos.initPet(); dao.clear(Pet.class); dao.insert(Pet.create(4)); List pets = dao.query(Pet.class, null, dao.createPager(1, 2)); @@ -213,4 +215,4 @@ public void test_issue_1281() { System.out.println(sb); assertEquals("id IN (select user_id from role where id in (?,?,?))", sb.toString()); } -} \ No newline at end of file +} diff --git a/test/org/nutz/dao/test/sqls/SQLFileParsingTest.java b/test/org/nutz/dao/test/sqls/SQLFileParsingTest.java index 158d963125..3599e19f3b 100644 --- a/test/org/nutz/dao/test/sqls/SQLFileParsingTest.java +++ b/test/org/nutz/dao/test/sqls/SQLFileParsingTest.java @@ -123,4 +123,12 @@ public void test_with_inline_comment() throws IOException { assertEquals("hi", sqls.keys()[0]); assertEquals("/*测试*/select 1 from t_pet", sqls.get("hi")); } + + @Test + public void test_with_row_sql() throws IOException { + FileSqlManager fileSqlManager=new FileSqlManager("org/nutz/dao/test/sqls/row.sqls"); + fileSqlManager.setByRow(true); + assertEquals(4, fileSqlManager.count()); + assertEquals("row.sqls.1", fileSqlManager.keys()[0]); + } } diff --git a/test/org/nutz/dao/test/sqls/SqlImplTest.java b/test/org/nutz/dao/test/sqls/SqlImplTest.java index ecd3a3d564..4fb8945b7d 100644 --- a/test/org/nutz/dao/test/sqls/SqlImplTest.java +++ b/test/org/nutz/dao/test/sqls/SqlImplTest.java @@ -1,32 +1,78 @@ -package org.nutz.dao.test.sqls; - -import static org.junit.Assert.*; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.junit.Test; -import org.nutz.dao.Sqls; -import org.nutz.dao.sql.Sql; - -public class SqlImplTest { - - @Test - public void test_sql_get_list() { - Sql sql = Sqls.create("SELECT version()"); - ArrayList> list = new ArrayList>(); - list.add(new HashMap()); - list.add(new HashMap()); - - sql.getContext().setResult(list); - - List re = sql.getList(Map.class);// 传入 map结果会导致上面的isFrom 为false - assertTrue(re == list); - - re = sql.getList(HashMap.class);// 因为list中的实例是HashMap,因此能够正常返回 - assertTrue(re == list); - } - -} +package org.nutz.dao.test.sqls; + +import org.junit.Test; +import org.nutz.dao.Sqls; +import org.nutz.dao.sql.Sql; +import org.nutz.dao.test.DaoCase; +import org.nutz.dao.test.meta.Pet; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class SqlImplTest extends DaoCase { + + @Test + public void test_sql_get_list() { + Sql sql = Sqls.create("SELECT version()"); + ArrayList> list = new ArrayList>(); + list.add(new HashMap()); + list.add(new HashMap()); + + sql.getContext().setResult(list); + + List re = sql.getList(Map.class);// 传入 map结果会导致上面的isFrom 为false + assertTrue(re == list); + + re = sql.getList(HashMap.class);// 因为list中的实例是HashMap,因此能够正常返回 + assertTrue(re == list); + } + + // https://nutz.cn/yvr/t/2emsd1ma36g99rmnqtq6o87skj + @Test + public void test_sql_with_many_vars() { + String str = Sqls.create("${a}${b}").setVar("a", 1).setVar("b", 2).toString(); + assertEquals(str, "12"); + + str = Sqls.create("${a}_${b}").setVar("a", 1).setVar("b", 2).toString(); + assertEquals(str, "1_2"); + + HashMap vars = new HashMap(); + vars.put("c", 3); + str = Sqls.create("${a}_${b}_${c}").setVars(vars).setVar("a", 1).setVar("b", 2).toString(); + assertEquals(str, "1_2_3"); + } + + @Test + public void test_sql_with_many_params() { + HashMap params = new HashMap(); + params.put("a", 1); + params.put("b", 2); + String str = Sqls.create("select * from x where a=@a and b=@b and c=@c").setParams(params).setParam("c", 3).toString(); + assertEquals(str, "select * from x where a=1 and b=2 and c=3"); + } + + @Test + public void test_sql_in_list() { + List list = new ArrayList(); + list.add("wendal"); + list.add("zozoh"); + list.add("pangwu"); + Sql sql = Sqls.queryEntity("select id from t_pet where name in (@list)"); + sql.setParam("list", list); + sql.setEntity(dao.getEntity(Pet.class)); + System.out.println(sql.forPrint()); + System.out.println(sql.toPreparedStatement()); + assertEquals(3, sql.getParamMatrix()[0].length); + dao.create(Pet.class, true); + dao.insert(Pet.create("wendal")); + dao.insert(Pet.create("zozoh")); + dao.insert(Pet.create("pangwu")); + List pets = dao.execute(sql).getList(Pet.class); + assertEquals(3, pets.size()); + } +} diff --git a/test/org/nutz/dao/test/sqls/row.sqls b/test/org/nutz/dao/test/sqls/row.sqls new file mode 100644 index 0000000000..c301eb3c82 --- /dev/null +++ b/test/org/nutz/dao/test/sqls/row.sqls @@ -0,0 +1,5 @@ +INSERT INTO t_abc (name) VALUES('Wizzer1'); +INSERT INTO t_abc (name) VALUES('Wizzer2'); + +INSERT INTO t_abc (name) VALUES('Wizzer3'),('Wizzer4'),('Wizzer5'); +INSERT INTO t_abc (name) VALUES('Wizzer6'); diff --git a/test/org/nutz/dao/texp/CndTest.java b/test/org/nutz/dao/texp/CndTest.java index 91f943d3c6..890c2088a4 100644 --- a/test/org/nutz/dao/texp/CndTest.java +++ b/test/org/nutz/dao/texp/CndTest.java @@ -12,6 +12,7 @@ import org.nutz.dao.Condition; import org.nutz.dao.FieldMatcher; import org.nutz.dao.entity.Entity; +import org.nutz.dao.sql.Criteria; import org.nutz.dao.test.DaoCase; import org.nutz.dao.test.meta.Pet; import org.nutz.dao.util.cri.SqlExpression; @@ -200,7 +201,158 @@ public void test_obj_read_write() { byte[] buf = Lang.toBytes(c); c = Lang.fromBytes(buf, Cnd.class); - assertEquals(" WHERE (f2=1) AND NOT (f3=1)", c.toString()); } + + /** + * Criteria 接口测试int[]数组 + */ + @Test + public void test_in_by_criteria_int_array () { + int[] ids = {1,2,3}; + Criteria cri = Cnd.cri(); + cri.where().andInIntArray2("nm", ids); + assertEquals(" WHERE nm IN (1,2,3)", cri.toString()); + } + + /** + * Criteria 接口测试List<Integer> + */ + @Test + public void test_in_by_criteria_int_list () { + List ids = new ArrayList(); + ids.add(1); + ids.add(2); + ids.add(3); + Criteria cri = Cnd.cri(); + cri.where().andInIntList("nm", ids); + assertEquals(" WHERE nm IN (1,2,3)", cri.toString()); + } + + /** + * Criteria 接口测试long[]数组 + */ + @Test + public void test_in_by_criteria_long_array () { + long[] ids = {1L,2L,3L}; + Criteria cri = Cnd.cri(); + cri.where().andInArray("nm", ids); + assertEquals(" WHERE nm IN (1,2,3)", cri.toString()); + } + + /** + * Criteria 接口测试List<Long> + */ + @Test + public void test_in_by_criteria_long_list () { + List ids = new ArrayList(); + ids.add(1L); + ids.add(2L); + ids.add(3L); + Criteria cri = Cnd.cri(); + cri.where().andInList("nm", ids); + assertEquals(" WHERE nm IN (1,2,3)", cri.toString()); + } + + /** + * Criteria 接口测试String[]数组 + */ + @Test + public void test_in_by_criteria_string_array () { + String[] ids = {"bj","sh","gz","sz"}; + Criteria cri = Cnd.cri(); + cri.where().andInStrArray("nm", ids); + assertEquals(" WHERE nm IN ('bj','sh','gz','sz')", cri.toString()); + } + + /** + * Criteria 接口测试List<String> + */ + @Test + public void test_in_by_criteria_string_list () { + List ids = new ArrayList(); + ids.add("bj"); + ids.add("sh"); + ids.add("gz"); + ids.add("sz"); + Criteria cri = Cnd.cri(); + cri.where().andInStrList("nm", ids); + assertEquals(" WHERE nm IN ('bj','sh','gz','sz')", cri.toString()); + } + + /** + * Criteria 接口测试int[]数组 + */ + @Test + public void test_not_in_by_criteria_int_array () { + int[] ids = {1,2,3}; + Criteria cri = Cnd.cri(); + cri.where().andNotInArray("nm", ids); + assertEquals(" WHERE nm NOT IN (1,2,3)", cri.toString()); + } + + /** + * Criteria 接口测试List<Integer> + */ + @Test + public void test_not_in_by_criteria_int_list () { + List ids = new ArrayList(); + ids.add(1); + ids.add(2); + ids.add(3); + Criteria cri = Cnd.cri(); + cri.where().andNotInIntList("nm", ids); + assertEquals(" WHERE nm NOT IN (1,2,3)", cri.toString()); + } + + /** + * Criteria 接口测试int[]数组 + */ + @Test + public void test_not_in_by_criteria_long_array () { + int[] ids = {1,2,3}; + Criteria cri = Cnd.cri(); + cri.where().andNotInArray("nm", ids); + assertEquals(" WHERE nm NOT IN (1,2,3)", cri.toString()); + } + + /** + * Criteria 接口测试List<Integer> + */ + @Test + public void test_not_in_by_criteria_long_list () { + List ids = new ArrayList(); + ids.add(1); + ids.add(2); + ids.add(3); + Criteria cri = Cnd.cri(); + cri.where().andNotInIntList("nm", ids); + assertEquals(" WHERE nm NOT IN (1,2,3)", cri.toString()); + } + + /** + * Criteria 接口测试String[]数组 + */ + @Test + public void test_not_in_by_criteria_string_array () { + String[] ids = {"bj","sh","gz","sz"}; + Criteria cri = Cnd.cri(); + cri.where().andNotInArray("nm", ids); + assertEquals(" WHERE nm NOT IN ('bj','sh','gz','sz')", cri.toString()); + } + + /** + * Criteria 接口测试List<String> + */ + @Test + public void test_not_in_by_criteria_string_list () { + List ids = new ArrayList(); + ids.add("bj"); + ids.add("sh"); + ids.add("gz"); + ids.add("sz"); + Criteria cri = Cnd.cri(); + cri.where().andNotInStrList("nm", ids); + assertEquals(" WHERE nm NOT IN ('bj','sh','gz','sz')", cri.toString()); + } } diff --git a/test/org/nutz/dao/texp/LamdaCndTest.java b/test/org/nutz/dao/texp/LamdaCndTest.java new file mode 100644 index 0000000000..568efc1289 --- /dev/null +++ b/test/org/nutz/dao/texp/LamdaCndTest.java @@ -0,0 +1,159 @@ +package org.nutz.dao.texp; + +import org.junit.Test; +import org.nutz.dao.Cnd; +import org.nutz.dao.Condition; +import org.nutz.dao.entity.Entity; +import org.nutz.dao.sql.Criteria; +import org.nutz.dao.sql.OrderBy; +import org.nutz.dao.test.DaoCase; +import org.nutz.dao.util.cri.Exps; +import org.nutz.dao.util.cri.SqlExpression; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +/** + * @author 黄川 huchuc@vip.qq.com + * @date: 2021/4/21 + */ +public class LamdaCndTest extends DaoCase { + + private Entity en; + + protected void before() { + en = dao.create(Worker.class, true); + } + + protected void after() { + } + + @Test + public void test_gt_like() { + Condition c = Cnd.where(Worker::getId, ">", 45) + .and(Worker::getName, "LIKE", "%ry%"); + String exp = "WHERE wid>45 AND wname LIKE '%ry%'"; + assertEquals(exp, c.toSql(en).trim()); + } + + @Test + public void test_bracket() { + String exp = "WHERE (wid>45) AND wname LIKE '%ry%'"; + Condition c0 = Cnd.where(Cnd.exps("id", ">", 45)) + .and("name", "LIKE", "%ry%"); + Condition c1 = Cnd.where(Cnd.exps(Worker::getId, ">", 45)) + .and(Worker::getName, "LIKE", "%ry%"); + assertEquals(exp, c0.toSql(en).trim()); + assertEquals(exp, c1.toSql(en).trim()); + } + + @Test + public void test_order() { + Condition c = Cnd.orderBy() + .asc(Worker::getId) + .desc(Worker::getName) + .asc(Worker::getAge) + .desc(Worker::getWorkingDay); + String exp = "ORDER BY wid ASC, wname DESC, age ASC, days DESC"; + assertEquals(exp, c.toSql(en).trim()); + assertEquals(exp, c.toSql(en).trim()); + } + + @Test + public void test_like_in() { + int[] ages = {4, 7, 9}; + SqlExpression e = Cnd.exps(Worker::getAge, ">", 35).and(Worker::getId, "<", 47); + SqlExpression e2 = Cnd.exps(Worker::getName, "\tLIKE ", "%t%") + .and(Worker::getAge, "IN \n\r", ages) + .or(e); + Condition c = Cnd.where(Worker::getId, "=", 37) + .and(e) + .or(e2) + .asc(Worker::getAge) + .desc(Worker::getId); + String exp = "WHERE wid=37 AND (age>35 AND wid<47) OR (wname LIKE '%t%' AND age IN (4,7,9) OR (age>35 AND wid<47)) ORDER BY age ASC, wid DESC"; + assertEquals(exp, c.toSql(en).trim()); + } + + + @Test + public void test_in_by_int_array() { + int[] ids = {3, 5, 7}; + Condition c = Cnd.where(Worker::getId, "iN", ids); + String exp = "WHERE id IN (3,5,7)"; + assertEquals(exp, c.toSql(null).trim()); + } + + @Test + public void test_in_by_int_list() { + List list = new ArrayList(); + list.add(3); + list.add(5); + list.add(7); + Condition c = Cnd.where(Worker::getId, "iN", list); + String exp = "WHERE id IN (3,5,7)"; + assertEquals(exp, c.toSql(null).trim()); + } + + + @Test + public void test_add_other_or_method_by_github_issuse_148() { + SqlExpression e1 = Cnd.exps(Worker::getCity, "=", "beijing") + .or(Worker::getCity, "=", "shanghai") + .or(Worker::getCity, "=", "guangzhou") + .or(Worker::getCity, "=", "shenzhen"); + SqlExpression e2 = Cnd.exps(Worker::getAge, ">", 18).and(Worker::getAge, "<", 30); + String exp = "WHERE (ct='beijing' OR ct='shanghai' OR ct='guangzhou' OR ct='shenzhen') AND (age>18 AND age<30)"; + assertEquals(exp, Cnd.where(e1).and(e2).toSql(en).trim()); + } + + + /** + * Criteria 接口测试List<String> + */ + @Test + public void test_not_in_by_criteria_string_list() { + List ids = new ArrayList(); + ids.add("bj"); + ids.add("sh"); + ids.add("gz"); + ids.add("sz"); + Criteria cri = Cnd.cri(); + cri.where().andNotInStrList(Worker::getId, ids); + assertEquals(" WHERE id NOT IN ('bj','sh','gz','sz')", cri.toString()); + } + + /** + * test_orderby + */ + @Test + public void test_orderby() { + OrderBy orderBy0 = Cnd.orderBy().desc(Worker::getCity).asc(Worker::getCity); + OrderBy orderBy1 = Cnd.orderBy().desc("ct").asc("ct"); + assertEquals(orderBy0.toSql(en), orderBy1.toSql(en)); + } + + /** + * test_orderby + */ + @Test + public void test_groupby() { + OrderBy orderBy0 = Cnd.cri().groupBy(Worker::getCity,Worker::getId); + OrderBy orderBy1 = Cnd.cri().groupBy("ct","id"); + assertEquals(orderBy0.toSql(en), orderBy1.toSql(en)); + } + + /** + * test_exps_insql2 + */ + @Test + public void test_exps_insql2() { + Cnd cnd0 = Cnd.where(Exps.inSql2(Worker::getId, "select user_id from role where id in (%s)", Arrays.asList(1,2,3))); + Cnd cnd1 = Cnd.where(Exps.inSql2("wid", "select user_id from role where id in (%s)", Arrays.asList(1,2,3))); + assertEquals(cnd0.toSql(en), cnd1.toSql(en)); + } + +} diff --git a/test/org/nutz/dao/texp/Worker.java b/test/org/nutz/dao/texp/Worker.java index 6884834b3a..095cea5ab1 100644 --- a/test/org/nutz/dao/texp/Worker.java +++ b/test/org/nutz/dao/texp/Worker.java @@ -22,4 +22,43 @@ public class Worker { @Column("days") public int workingDay; + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public short getAge() { + return age; + } + + public void setAge(short age) { + this.age = age; + } + + public int getWorkingDay() { + return workingDay; + } + + public void setWorkingDay(int workingDay) { + this.workingDay = workingDay; + } } diff --git a/test/org/nutz/dao/util/DaoUpTest.java b/test/org/nutz/dao/util/DaoUpTest.java index 89b87cd88f..50f59d3c9f 100644 --- a/test/org/nutz/dao/util/DaoUpTest.java +++ b/test/org/nutz/dao/util/DaoUpTest.java @@ -479,7 +479,7 @@ public void test_links() { assertNotNull(rootTeam.getJobs()); assertEquals(15, rootTeam.getJobs().size()); - // 移除前11个任务的引用 + // 移除前11个任务的引用, 在下面的deleteLinks执行的时候, 它们就会幸免于难 for (int i = 0; i < 11; i++) { rootTeam.getJobs().remove(0); } diff --git a/test/org/nutz/dao/util/TestUtil.java b/test/org/nutz/dao/util/TestUtil.java new file mode 100644 index 0000000000..97c7ff5b3b --- /dev/null +++ b/test/org/nutz/dao/util/TestUtil.java @@ -0,0 +1,37 @@ +package org.nutz.dao.util; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; + +/** + * @author Haiming + * @date 2020/8/14 9:22 AM + */ +public class TestUtil { + /** + * 读取文件 + * + * @param path + * @return + */ + public static String getFileData(String path) { + ClassLoader cl = TestUtil.class.getClassLoader(); + InputStream is = cl.getResourceAsStream(path); + //读取文件 + StringBuffer sb = new StringBuffer(); + //这里可以控制编码 + BufferedReader br = null; + try { + br = new BufferedReader(new InputStreamReader(is, "UTF-8")); + String line = null; + while ((line = br.readLine()) != null) { + sb.append(line); + } + } catch (Exception e) { + e.printStackTrace(); + } + String data = new String(sb); + return data; + } +} diff --git a/test/org/nutz/el/El2Test.java b/test/org/nutz/el/El2Test.java index cf9496cec7..bab5cdf39c 100644 --- a/test/org/nutz/el/El2Test.java +++ b/test/org/nutz/el/El2Test.java @@ -594,4 +594,30 @@ public void test_issue_1229() { assertEquals("wendal", El.eval(ctx, "!!(obj.pet.name) ||| 'wendal'")); assertEquals("dog", El.eval(ctx, "!!(obj.girls) ||| 'dog'")); } + + @Test + public void test_issue_1475_1476() { + + Context context = Lang.context(); + context.set("Math", Math.class); + + + //Queue rpn = new ShuntingYard().parseToRPN("Math.max(10, 0-11)"); + //System.out.println(rpn); + +// Queue rpn = new ShuntingYard().parseToRPN("Math.max(0,-10)"); +// System.out.println(rpn); + Object max = El.eval(context, "Math.max(0,-11)"); + assertEquals(0, max); + + + assertEquals(0, El.eval(context, "Math.max(-1,0)")); + assertEquals(0, El.eval(context, "Math.max(0,-1)")); + assertEquals(0, El.eval(context, "Math.max(-0,-1)")); + + + assertEquals(0, El.eval(context, "Math.max(-1,Math.max(-1,Math.max(-1,Math.max(-1,0))))")); + assertEquals(0, El.eval(context, "Math.max(Math.max(Math.max(Math.max(0,-1),-1),-1),-1)")); + assertEquals(0, El.eval(context, "Math.max(-Math.max(-Math.max(-Math.max(-0,-1),-1),-1),-1)")); + } } diff --git a/test/org/nutz/filepool/NutFilePoolTest.java b/test/org/nutz/filepool/NutFilePoolTest.java index 816623f020..187fb4f116 100644 --- a/test/org/nutz/filepool/NutFilePoolTest.java +++ b/test/org/nutz/filepool/NutFilePoolTest.java @@ -113,4 +113,25 @@ public void run() { Files.deleteDir(new File(home)); //assertFalse(res[0]); } + + @Test + public void test_getOrCreatePool() { + String home = Disks.normalize("~/tmp_nutz"); + new File(home).delete(); + new File(home).mkdirs(); + FilePool filePool =NutFilePool.getOrCreatePool(home, 20); + assertNotNull(filePool); + filePool.clear(); + assertEquals(0,filePool.current()); + + filePool.createDir(); + assertEquals(1,filePool.current()); + File file = filePool.createFile("tmp"); + + assertNotNull(file); + assertNotNull(file.getPath()); + filePool.removeFile(1, "tmp"); + assertFalse(filePool.hasFile(1, "tmp")); + assertEquals(2,filePool.current()); + } } diff --git a/test/org/nutz/filepool/SimpleFilePoolTest.java b/test/org/nutz/filepool/SimpleFilePoolTest.java new file mode 100644 index 0000000000..f93cf42ccd --- /dev/null +++ b/test/org/nutz/filepool/SimpleFilePoolTest.java @@ -0,0 +1,32 @@ +package org.nutz.filepool; + +import org.junit.Test; + +import java.io.File; + +import static org.junit.Assert.*; + +/** + * @Author: Haimming + * @Date: 2020-01-16 15:19 + * @Version 1.0 + */ +public class SimpleFilePoolTest { + + @Test + public void hasFile() { + SimpleFilePool simpleFilePool =new SimpleFilePool(System.getProperty("java.io.tmpdir")+"/simpleFilePool",6); + simpleFilePool.clear(); + assertEquals(0,simpleFilePool.current()); + simpleFilePool.createDir(); + assertEquals(1,simpleFilePool.current()); + File file =simpleFilePool.createFile("testFile"); + assertNotNull(file); + assertNotNull(file.getPath()); + assertEquals(2,simpleFilePool.current()); +// long id =simpleFilePool.getFileId(file); +// System.out.println(id); +// simpleFilePool.hasFile(id,file.getPath()); + + } +} \ No newline at end of file diff --git a/test/org/nutz/filepool/UU32FilePoolTest.java b/test/org/nutz/filepool/UU32FilePoolTest.java new file mode 100644 index 0000000000..6483b3aff5 --- /dev/null +++ b/test/org/nutz/filepool/UU32FilePoolTest.java @@ -0,0 +1,26 @@ +package org.nutz.filepool; + +import org.junit.Test; + +import java.io.File; + +import static org.junit.Assert.*; + +/** + * @Author: Haimming + * @Date: 2020-01-16 15:58 + * @Version 1.0 + */ +public class UU32FilePoolTest { + + @Test + public void creatPoolTest() { + UU32FilePool pool =new UU32FilePool(System.getProperty("java.io.tmpdir")+"/UU32FilePool"); + pool.clear(); + File file =pool.createFile("testFile"); + assertNotNull(file); + assertNotNull(file.getPath()); +// System.out.println(pool.getFileId(file)); + + } +} \ No newline at end of file diff --git a/test/org/nutz/http/HttpTest.java b/test/org/nutz/http/HttpTest.java index c4207399d0..6eb24aecab 100644 --- a/test/org/nutz/http/HttpTest.java +++ b/test/org/nutz/http/HttpTest.java @@ -14,10 +14,12 @@ public class HttpTest { public void testGet() { Response response = Http.get("http://nutztest.herokuapp.com/"); assertNotNull(response); + assertNotNull(response.getContent("UTF-8")); + assertNotNull(response.getContent()); assertNotNull(response.getContent()); assertNotNull(response.getDetail()); assertNotNull(response.getHeader()); - assertNotNull(response.getProtocal()); + assertNotNull(response.getProtocol()); assertTrue(response.getStatus() > 0); assertNotNull(response.getStream()); } @@ -62,6 +64,12 @@ public void test_https() { assertTrue(response.getStatus() == 200); } + @Test + public void test_http_req() { + Response response = Http.httpReq("https://nutz.cn",Request.METHOD.GET,null,null,5000, Sender.Default_Conn_Timeout); + assertTrue(response.getStatus() == 200); + } + @Test public void test_getBoundary() { String boundary = Http.multipart.getBoundary("multipart/form-data; charset=utf-8; boundary=0xKhTmLbOuNdArY-1593BCBB-3B9B-433B-8BC0-4B768CDA81CF"); diff --git a/test/org/nutz/img/ImagesTest.java b/test/org/nutz/img/ImagesTest.java index 354186e85d..13b5dc578c 100644 --- a/test/org/nutz/img/ImagesTest.java +++ b/test/org/nutz/img/ImagesTest.java @@ -1,9 +1,15 @@ package org.nutz.img; +import java.awt.*; +import java.awt.image.BufferedImage; import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import org.junit.Assert; import org.junit.Test; import org.nutz.lang.Files; +import org.nutz.lang.random.R; public class ImagesTest { @@ -21,4 +27,261 @@ public void test_clipScale_url() throws Throwable { + "/snapshot.jpg"); Images.clipScale(file.toURI().toURL(), File.createTempFile("abc", "jpg"), 256, 256); } + + @Test + public void createCaptcha() { + String text = R.captchaChar(4); + int w = 145; + int h = 35; + BufferedImage img = Images.createCaptcha(text, w, h, null, "FFF", null); + Assert.assertNotNull(img); + Assert.assertNotNull(img.getSource()); + Assert.assertEquals(img.getHeight(),h); + Assert.assertEquals(img.getWidth(),w); + } + + @Test + public void createCaptcha1() { + BufferedImage img =Images.createCaptcha("小明"); + Assert.assertNotNull(img); + Assert.assertNotNull(img.getSource()); + } + + @Test + public void rotate() { + BufferedImage img =Images.createAvatar("小明"); + img = Images.rotate(img, 90); + Assert.assertNotNull(img); + Assert.assertNotNull(img.getSource()); + + } + + @Test + public void rotate1() { + } + + @Test + public void rotate2() { + } + + @Test + public void zoomScale() { + BufferedImage img =Images.createAvatar("小明"); + img = Images.zoomScale(img, 160, 180, Color.WHITE); + Assert.assertNotNull(img); + Assert.assertNotNull(img.getSource()); + } + + @Test + public void zoomScale1() { + } + + @Test + public void zoomScale2() { + } + + @Test + public void zoomScale3() { + } + + @Test + public void scale() { + } + + @Test + public void clipScale() { + String text = R.captchaChar(4); + int w = 145; + int h = 35; + BufferedImage srcImg = Images.createText(text, w, h, null, "FFF", null,0,2); + BufferedImage img = Images.clipScale(srcImg, w-10, h-10); + Assert.assertNotNull(img); + Assert.assertNotNull(img.getSource()); + Assert.assertEquals(img.getHeight(),h-10); + Assert.assertEquals(img.getWidth(),w-10); + + } + + @Test + public void clipScale1() { + } + + @Test + public void clipScale2() { + } + + @Test + public void clipScale3() { + } + + @Test + public void clipScale4() { + } + + @Test + public void clipScale5() { + } + + @Test + public void flipHorizontal() { + BufferedImage img =Images.createAvatar("小明"); + img = Images.flipHorizontal(img); + Assert.assertNotNull(img); + Assert.assertNotNull(img.getSource()); + + } + + @Test + public void flipHorizontal1() { + } + + @Test + public void flipVertical() { + BufferedImage img =Images.createAvatar("小明"); + img = Images.flipVertical(img); + Assert.assertNotNull(img); + Assert.assertNotNull(img.getSource()); + } + + @Test + public void flipVertical1() { + } + + @Test + public void twist() { + BufferedImage img =Images.createAvatar("小明"); + img = Images.twist(img, 1, "#FFF"); + Assert.assertNotNull(img); + Assert.assertNotNull(img.getSource()); + } + + @Test + public void addWatermark() { + } + + @Test + public void grayImage() { + BufferedImage img =Images.createAvatar("小明"); + BufferedImage img2 =Images.grayImage(img); + Assert.assertNotNull(img2); + Assert.assertNotNull(img2.getSource()); + + + } + + @Test + public void multiply() { + BufferedImage bgImg =Images.createAvatar("小明"); + String text = R.captchaChar(4); + BufferedImage itemImg = Images.createText(text); + BufferedImage img =Images.multiply(bgImg, itemImg, 0, 0); + Assert.assertNotNull(img); + Assert.assertNotNull(img.getSource()); + } + + @Test + public void cutoutByLuminance() { + BufferedImage srcImg =Images.createAvatar("小明"); + BufferedImage img = Images.cutoutByLuminance(srcImg); + Assert.assertNotNull(img); + Assert.assertNotNull(img.getSource()); + } + + @Test + public void cutoutByChannel() { + BufferedImage srcImg =Images.createAvatar("小明"); + BufferedImage img = Images.cutoutByChannel(srcImg, Images.CHANNEL_BLUE); + Assert.assertNotNull(img); + Assert.assertNotNull(img.getSource()); + } + + @Test + public void cutoutByPixel() { + BufferedImage srcImg =Images.createAvatar("小明"); + BufferedImage img = Images.cutoutByPixel(srcImg, 0, 0, 20); + Assert.assertNotNull(img); + Assert.assertNotNull(img.getSource()); + + } + + @Test + public void channelImage() { + BufferedImage img =Images.createAvatar("小明"); + BufferedImage img2 =Images.channelImage(img,Images.CHANNEL_RED); + Assert.assertNotNull(img2); + Assert.assertNotNull(img2.getSource()); + } + + @Test + public void read() { + // 可以是URL对象 + try { + BufferedImage img =Images.read(new URL("https://www.baidu.com/img/bdlogo.png")); + Assert.assertNotNull(img); + Assert.assertNotNull(img.getSource()); + } catch (MalformedURLException e) { + e.printStackTrace(); + } + + } + + @Test + public void write() { + } + + @Test + public void write1() { + } + + @Test + public void writeAndClose() { + } + + @Test + public void writeJpeg() { + } + + @Test + public void encodeBase64() { + } + + @Test + public void encodeBase641() { + } + + @Test + public void redraw() { + } + + @Test + public void createText() { + String text = R.captchaChar(4); + int w = 145; + int h = 35; + BufferedImage img = Images.createText(text, w, h, null, "FFF", null,0,2); + Assert.assertNotNull(img); + Assert.assertNotNull(img.getSource()); + Assert.assertEquals(img.getHeight(),h); + Assert.assertEquals(img.getWidth(),w); + } + + @Test + public void createText1() { + String text = R.captchaChar(4); + BufferedImage img = Images.createText(text); + Assert.assertNotNull(img); + Assert.assertNotNull(img.getSource()); + } + + @Test + public void createAvatar() { + BufferedImage img =Images.createAvatar("小明"); + Assert.assertNotNull(img); + Assert.assertNotNull(img.getSource()); + img = Images.createAvatar("小二", 256, "rgba(255,0,0,1)", "rgb(0,0,255)", "微软雅黑", 64, Font.ITALIC); + Assert.assertNotNull(img); + Assert.assertNotNull(img.getSource()); + } + + } diff --git a/test/org/nutz/ioc/aop/config/impl/JsonAopConfigrationTest.java b/test/org/nutz/ioc/aop/config/impl/JsonAopConfigrationTest.java index cb493c8563..8967301af4 100644 --- a/test/org/nutz/ioc/aop/config/impl/JsonAopConfigrationTest.java +++ b/test/org/nutz/ioc/aop/config/impl/JsonAopConfigrationTest.java @@ -1,32 +1,27 @@ package org.nutz.ioc.aop.config.impl; -import static org.junit.Assert.*; - -import org.junit.Assert; import org.junit.Test; -import org.nutz.Nutzs; -import org.nutz.ioc.Ioc; -import org.nutz.ioc.impl.NutIoc; -import org.nutz.ioc.loader.json.JsonLoader; public class JsonAopConfigrationTest { @Test public void test_jsonAop(){ - Nutzs.cd(); - Ioc ioc = new NutIoc(new JsonLoader("org/nutz/ioc/aop/config/impl/jsonfile-aop.js")); - Assert.assertTrue(ioc.getNames().length > 0); - for (String name : ioc.getNames()) { - ioc.get(null, name); - } - MyMI mi = ioc.get(MyMI.class, "myMI"); - assertTrue(mi.getTime() == 0); - Pet2 pet2 = ioc.get(Pet2.class,"pet2"); - pet2.sing(); - assertTrue(mi.getTime() == 1); - pet2.sing(); - assertTrue(mi.getTime() == 2); - ioc.depose(); +// Nutzs.cd(); +// NutConf.AOP_USE_CLASS_ID = true; +// Ioc ioc = new NutIoc(new JsonLoader("org/nutz/ioc/aop/config/impl/jsonfile-aop.js")); +// Assert.assertTrue(ioc.getNames().length > 0); +// for (String name : ioc.getNames()) { +// ioc.get(null, name); +// } +// MyMI mi = ioc.get(MyMI.class, "myMI"); +// assertTrue(mi.getTime() == 0); +// Pet2 pet2 = ioc.get(Pet2.class,"pet2"); +// pet2.sing(); +// assertTrue(mi.getTime() == 1); +// pet2.sing(); +// assertTrue(mi.getTime() == 2); +// ioc.depose(); +// NutConf.AOP_USE_CLASS_ID = false; } } diff --git a/test/org/nutz/ioc/impl/PropertiesProxyTest.java b/test/org/nutz/ioc/impl/PropertiesProxyTest.java index ca4a66fcca..412b5c92f8 100644 --- a/test/org/nutz/ioc/impl/PropertiesProxyTest.java +++ b/test/org/nutz/ioc/impl/PropertiesProxyTest.java @@ -1,7 +1,12 @@ package org.nutz.ioc.impl; +import static org.junit.matchers.JUnitMatchers.either; + import java.io.UnsupportedEncodingException; +import java.util.Arrays; +import java.util.List; +import org.hamcrest.core.Is; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -58,4 +63,25 @@ public void testSize() { Assert.assertEquals(pp.getKeys().size(), 4); Assert.assertEquals(pp.getValues().size(), 4); } + + + @Test + public void testPrefix() throws Exception { + + PropertiesProxy proxy = new PropertiesProxy(true, "config/prefix.properties"); + + assertPrefix(proxy, "test"); + assertPrefix(proxy, "test."); + + + + } + + private void assertPrefix(PropertiesProxy proxy, String prefix) { + List prefixedKeys = proxy.getKeysWithPrefix(prefix); + // order is required + Assert.assertThat(prefixedKeys, + either(Is.is(Arrays.asList("test.p1", "test.p2"))) + .or(Is.is(Arrays.asList("test.p2", "test.p1")))); + } } diff --git a/test/org/nutz/ioc/json/AopJsonIocTest.java b/test/org/nutz/ioc/json/AopJsonIocTest.java index 3d6277eec9..b30baaf7de 100644 --- a/test/org/nutz/ioc/json/AopJsonIocTest.java +++ b/test/org/nutz/ioc/json/AopJsonIocTest.java @@ -3,7 +3,7 @@ import static org.junit.Assert.*; import org.junit.Test; -import org.nutz.Nutzs; +import org.nutz.conf.NutConf; import org.nutz.ioc.Ioc; import org.nutz.ioc.IocLoader; import org.nutz.ioc.impl.NutIoc; @@ -14,19 +14,25 @@ public class AopJsonIocTest { @Test public void test_simple() { - Nutzs.cd(); - IocLoader il = new JsonLoader("org/nutz/ioc/json/aop.js"); - Ioc ioc = new NutIoc(il); - StringBuilder sb = ioc.get(StringBuilder.class, "sb"); - Mammal fox = ioc.get(Mammal.class, "fox"); + NutConf.AOP_USE_CLASS_ID = true; + try { - assertEquals("Fox", fox.getName()); - assertEquals("B:getName0;A:getName0;", sb.toString()); - sb.delete(0, sb.length()); - fox.getName(); - fox.getName(); - assertEquals("B:getName0;A:getName0;B:getName0;A:getName0;", sb.toString()); + IocLoader il = new JsonLoader("org/nutz/ioc/json/aop.js"); + Ioc ioc = new NutIoc(il); + StringBuilder sb = ioc.get(StringBuilder.class, "sb"); + Mammal fox = ioc.get(Mammal.class, "fox"); - ioc.depose(); + assertEquals("Fox", fox.getName()); + assertEquals("B:getName0;A:getName0;", sb.toString()); + sb.delete(0, sb.length()); + fox.getName(); + fox.getName(); + assertEquals("B:getName0;A:getName0;B:getName0;A:getName0;", sb.toString()); + + ioc.depose(); + } + finally { + NutConf.AOP_USE_CLASS_ID = false; + } } } diff --git a/test/org/nutz/ioc/loader/annotation/AnnotationIocLoaderTest.java b/test/org/nutz/ioc/loader/annotation/AnnotationIocLoaderTest.java index ffe5085d7e..49bd129806 100644 --- a/test/org/nutz/ioc/loader/annotation/AnnotationIocLoaderTest.java +++ b/test/org/nutz/ioc/loader/annotation/AnnotationIocLoaderTest.java @@ -13,8 +13,10 @@ import org.nutz.ioc.impl.NutIoc; import org.nutz.ioc.loader.annotation.meta.Issue1060; import org.nutz.ioc.loader.annotation.meta.issue1280.Issue1280Bean; +import org.nutz.ioc.loader.annotation.meta.issue1427.Issue1427Top; import org.nutz.ioc.meta.IocObject; import org.nutz.json.Json; +import org.nutz.lang.util.NutMap; import org.nutz.log.Logs; public class AnnotationIocLoaderTest { @@ -67,4 +69,24 @@ public void test_ioc_iocbean_method() throws ObjectLoadException { assertNotNull(ioc.get(Dao.class, "dao3")); ioc.depose(); } + + @Test + public void test_issue_1427() throws ObjectLoadException { + AnnotationIocLoader loader = new AnnotationIocLoader(Issue1427Top.class.getPackage().getName()); + assertTrue(loader.has("issue_1427_mapa")); + assertTrue(loader.has("issue_1427_mapb")); + assertTrue(loader.has("issue_1427_mapc")); + assertTrue(loader.has("issue1427AAA")); + assertTrue(loader.has("issue1427BBB")); + NutIoc ioc = new NutIoc(loader); + assertNotNull(ioc.get(NutMap.class, "issue_1427_mapa")); + assertNotNull(ioc.get(NutMap.class, "issue_1427_mapb")); + assertNotNull(ioc.get(NutMap.class, "issue_1427_mapc")); + assertNotNull(ioc.get(Issue1427Top.class, "issue1427AAA")); + assertNotNull(ioc.get(Issue1427Top.class, "issue1427BBB")); + assertEquals(ioc.get(Issue1427Top.class, "issue1427AAA"), ioc.get(NutMap.class, "issue_1427_mapa").get("obj")); + assertEquals(ioc.get(Issue1427Top.class, "issue1427BBB"), ioc.get(NutMap.class, "issue_1427_mapb").get("obj")); + assertEquals(ioc.get(Issue1427Top.class, "issue1427BBB"), ioc.get(NutMap.class, "issue_1427_mapc").get("obj")); + ioc.depose(); + } } diff --git a/test/org/nutz/ioc/loader/annotation/meta/issue1427/Issue1427AAA.java b/test/org/nutz/ioc/loader/annotation/meta/issue1427/Issue1427AAA.java new file mode 100644 index 0000000000..ffe1f3db97 --- /dev/null +++ b/test/org/nutz/ioc/loader/annotation/meta/issue1427/Issue1427AAA.java @@ -0,0 +1,8 @@ +package org.nutz.ioc.loader.annotation.meta.issue1427; + +import org.nutz.ioc.loader.annotation.IocBean; + +@IocBean +public class Issue1427AAA implements Issue1427Top { + +} diff --git a/test/org/nutz/ioc/loader/annotation/meta/issue1427/Issue1427BBB.java b/test/org/nutz/ioc/loader/annotation/meta/issue1427/Issue1427BBB.java new file mode 100644 index 0000000000..4e15fb1a42 --- /dev/null +++ b/test/org/nutz/ioc/loader/annotation/meta/issue1427/Issue1427BBB.java @@ -0,0 +1,8 @@ +package org.nutz.ioc.loader.annotation.meta.issue1427; + +import org.nutz.ioc.loader.annotation.IocBean; + +@IocBean +public class Issue1427BBB implements Issue1427Top { + +} diff --git a/test/org/nutz/ioc/loader/annotation/meta/issue1427/Issue1427Beans.java b/test/org/nutz/ioc/loader/annotation/meta/issue1427/Issue1427Beans.java new file mode 100644 index 0000000000..47f20595bc --- /dev/null +++ b/test/org/nutz/ioc/loader/annotation/meta/issue1427/Issue1427Beans.java @@ -0,0 +1,27 @@ +package org.nutz.ioc.loader.annotation.meta.issue1427; + +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.lang.util.NutMap; + +@IocBean +public class Issue1427Beans { + + // 注入的应该是issue1427AAA + @IocBean(name="issue_1427_mapa") + public NutMap makeMapA(Issue1427Top issue1427AAA) { + return new NutMap("obj", issue1427AAA); + } + + // 注入的应该是refer:issue1427BBB + @IocBean(name="issue_1427_mapb") + public NutMap makeMapB(@Inject("refer:issue1427BBB") Issue1427Top issue1427BBB) { + return new NutMap("obj", issue1427BBB); + } + + // 注入的应该是refer:issue1427BBB + @IocBean(name="issue_1427_mapc") + public NutMap makeMapC(@Inject("refer:issue1427BBB") Issue1427Top issue1427AAA) { + return new NutMap("obj", issue1427AAA); + } +} diff --git a/test/org/nutz/ioc/loader/annotation/meta/issue1427/Issue1427Top.java b/test/org/nutz/ioc/loader/annotation/meta/issue1427/Issue1427Top.java new file mode 100644 index 0000000000..edb6eb658b --- /dev/null +++ b/test/org/nutz/ioc/loader/annotation/meta/issue1427/Issue1427Top.java @@ -0,0 +1,5 @@ +package org.nutz.ioc.loader.annotation.meta.issue1427; + +public interface Issue1427Top { + +} diff --git a/test/org/nutz/json/JsonTest.java b/test/org/nutz/json/JsonTest.java index cac43e4722..57301d3c08 100644 --- a/test/org/nutz/json/JsonTest.java +++ b/test/org/nutz/json/JsonTest.java @@ -14,15 +14,19 @@ import java.io.OutputStreamWriter; import java.io.StringWriter; import java.io.Writer; +import java.text.ParseException; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.HashMap; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import org.json.JSONException; import org.junit.Ignore; import org.junit.Test; import org.nutz.castor.Castors; @@ -35,6 +39,7 @@ import org.nutz.json.generic.IntKeyMap; import org.nutz.json.impl.JsonRenderImpl; import org.nutz.json.meta.EnumWithFields; +import org.nutz.json.meta.IntelliUserRespVo; import org.nutz.json.meta.Issue1199; import org.nutz.json.meta.JA; import org.nutz.json.meta.JB; @@ -46,6 +51,7 @@ import org.nutz.json.meta.Msg; import org.nutz.json.meta.MyDate2StringCastor; import org.nutz.json.meta.OuterClass; +import org.nutz.json.meta.PojoWithLocalDateTime; import org.nutz.lang.Files; import org.nutz.lang.Lang; import org.nutz.lang.Streams; @@ -54,14 +60,36 @@ import org.nutz.lang.util.NutMap; import org.nutz.lang.util.NutType; import org.nutz.lang.util.PType; +import org.skyscreamer.jsonassert.JSONAssert; @SuppressWarnings({"unchecked"}) public class JsonTest { + class Issue1393 { + final String name; + final int age; + + public Issue1393(String name, int age) { + this.name = name; + this.age = age; + } + } + + /** + * for issue https://github.com/nutzam/nutz/issues/1393 + */ + @Test + public void test_final_field() { + Issue1393 obj = new Issue1393("test1", 99); + String json = Json.toJson(obj, JsonFormat.compact()); + assertJsonEqualsNonStrict("{\"name\":\"test1\",\"age\":99}", json); + } + @JsonShape(Type.OBJECT) public static enum TT { T("t", 1); + String name; int index; @@ -112,11 +140,21 @@ public static enum K { K, T } + private void assertJsonEqualsNonStrict(String json1, String json2) { + try { + JSONAssert.assertEquals(json1, json2, false); + } + catch (JSONException jse) { + throw new IllegalArgumentException(jse.getMessage()); + } + } + @Test public void test_enum() { assertEquals("\"K\"", Json.toJson(K.K)); String expected = "{\n" + " \"name\": \"t\",\n" + " \"index\": 1\n" + "}"; - assertEquals(expected, Json.toJson(TT.T)); + assertJsonEqualsNonStrict(expected, Json.toJson(TT.T)); + assertEquals("\"T\"", Json.toJson(TT.T, JsonFormat.full().ignoreJsonShape())); } @Test @@ -154,7 +192,7 @@ public void test_region_as_String() { @Test public void test_empty_obj_toJson() { String j = Json.toJson(new Person(), JsonFormat.compact().setQuoteName(true)); - assertEquals("{\"age\":0,\"num\":0}", j); + assertJsonEqualsNonStrict("{\"age\":0,\"num\":0}", j); } @SuppressWarnings("rawtypes") @@ -722,7 +760,7 @@ public void testDuplicateArrayList() { a.list2.add("aaa"); String json = Json.toJson(a, JsonFormat.compact().setQuoteName(false)); String exp = "{list1:[\"aaa\"],list2:[\"aaa\"]}"; - assertEquals(exp, json); + assertJsonEqualsNonStrict(exp, json); } @Test @@ -1003,7 +1041,7 @@ public void test_number_formt_tojson() { num.setNum1(1); String a = "{\n" + " \"num1\": \"01.00\",\n" + " \"num2\": \"02.00\"\n" + "}"; String str = Json.toJson(num); - assertEquals(a, str); + assertJsonEqualsNonStrict(a, str); System.out.println(str); } @@ -1114,7 +1152,7 @@ public void test_issue_1285() throws IOException { assertEquals(METHOD.valueOf("POST"), map.get("post")); Json.fromJson(METHOD.class, "'POST'"); } - + @Test public void test_map_use_int_key_issue_1332() { String str = "{abc : {1:1}}"; @@ -1122,9 +1160,110 @@ public void test_map_use_int_key_issue_1332() { System.out.println(map); assertTrue(map.getAbc().containsKey(1)); } - + @Test public void test_t() { System.out.println(Json.toJson(new NutMap("abc", EnumWithFields.STAY_PUSH))); } + + @Test + public void test_new_toJson() { + System.out.println(Json.toJson(new NutMap("name", "t").addv("index", 1))); + System.out.println(Json.toJson(new NutMap("date", LocalDateTime.now()))); + } + + @Test + public void test_locale_fromJson() { + LocalDateTime dt = Json.fromJson(LocalDateTime.class, "'2018-02-20 21:53:39'"); + System.out.println(dt); + assertNotNull(dt); + + PojoWithLocalDateTime pojo = Json.fromJson(PojoWithLocalDateTime.class, "{localdt:'2018-02-20 21:53:39'}"); + System.out.println(pojo.localdt); + assertNotNull(pojo.localdt); + } + + @Test + public void test_locale_toJson() { + LocalDateTime dt = Json.fromJson(LocalDateTime.class, "'2018-02-20 21:53:39'"); + String json = Json.toJson(dt, JsonFormat.compact().setDateFormat("yyyy-MM-dd HH:mm:ss")); + System.out.println(json); + assertEquals(json, "\"2018-02-20 21:53:39\""); + + PojoWithLocalDateTime pojo = Json.fromJson(PojoWithLocalDateTime.class, "{localdt:'2018-02-20 21:53:39'}"); + json = Json.toJson(pojo, JsonFormat.compact().setDateFormat("yyyy-MM-dd HH:mm:ss")); + System.err.println(json); + System.out.println(pojo.localdt); + assertNotNull(pojo.localdt); + } + + @Test + public void test_json_lost_exception_message() throws Exception { + + PojoABC pojo = new PojoABC(); + try { + Json.toJson(pojo); + + } + catch (Exception e) { + e.printStackTrace(); + assertEquals(e.getMessage(), pojo.message); + + } + } + + public static class PojoABC { + + String message = "this is my message"; + + public String toJson() { + throw new RuntimeException(message); + } + + } + + // @Test + // public void test_instant_field() throws ParseException { + // Instant instant = Times.parse("yyyy-MM-dd HH:mm:ss", "2018-06-30 + // 18:27:10").toInstant(); + // String json = + // Json.toJson(instant,JsonFormat.compact().setDateFormat("yyyy-MM-dd + // HH:mm:ss")); + // assertEquals("\"2018-06-30 18:27:10\"", json); + // } + + @Test + public void test_bignumber() throws ParseException { + String json = "{abc:10012319000008971306}"; + Object re = Json.fromJson(json); + System.out.println(Json.toJson(re)); + } + + + @Test + public void test_long_as_string() throws ParseException { + NutMap map = new NutMap(); + map.put("abc", 12345678990012L); + System.out.println(Json.toJson(map, JsonFormat.full())); + System.out.println(Json.toJson(map, JsonFormat.full().setLongAsString(true))); + } + + @Test + public void test_nutzcn_abc() throws IOException { + byte[] buff = Streams.readBytes(getClass().getClassLoader().getResourceAsStream("org/nutz/json/pojo123.json")); + IntelliUserRespVo resp = Json.fromJson(IntelliUserRespVo.class, new String(buff)); + assertNotNull(resp); + assertNotNull(resp.getData()); + assertTrue(resp.getData().size() > 0); + assertTrue(resp.getData().get(0).getXm() != null); + System.out.println(Json.toJson(resp)); + } + + @Test + public void test_json_as_array() throws IOException { + String json = "[{age:1}]"; + for (int i = 0; i < 100*10000; i++) { + Json.fromJsonAsArray(Pet.class, json); + } + } } diff --git a/test/org/nutz/json/meta/EnumWithFields.java b/test/org/nutz/json/meta/EnumWithFields.java index a5bd785a4d..92202c6d79 100644 --- a/test/org/nutz/json/meta/EnumWithFields.java +++ b/test/org/nutz/json/meta/EnumWithFields.java @@ -17,4 +17,12 @@ public enum EnumWithFields { this.code = code; this.description = description; } + + public String getCode() { + return code; + } + + public String getDescription() { + return description; + } } diff --git a/test/org/nutz/json/meta/IntelliUserRespVo.java b/test/org/nutz/json/meta/IntelliUserRespVo.java new file mode 100644 index 0000000000..a219133872 --- /dev/null +++ b/test/org/nutz/json/meta/IntelliUserRespVo.java @@ -0,0 +1,43 @@ +package org.nutz.json.meta; + +import java.util.List; + +public class IntelliUserRespVo { + private List data; + private Integer errCode; + private Integer maxPage; + private Integer total; + private Integer count; + public List getData() { + return data; + } + public void setData(List data) { + this.data = data; + } + public Integer getErrCode() { + return errCode; + } + public void setErrCode(Integer errCode) { + this.errCode = errCode; + } + public Integer getMaxPage() { + return maxPage; + } + public void setMaxPage(Integer maxPage) { + this.maxPage = maxPage; + } + public Integer getTotal() { + return total; + } + public void setTotal(Integer total) { + this.total = total; + } + public Integer getCount() { + return count; + } + public void setCount(Integer count) { + this.count = count; + } + +} + \ No newline at end of file diff --git a/test/org/nutz/json/meta/IntellifUserAuthVo.java b/test/org/nutz/json/meta/IntellifUserAuthVo.java new file mode 100644 index 0000000000..02c8576cb4 --- /dev/null +++ b/test/org/nutz/json/meta/IntellifUserAuthVo.java @@ -0,0 +1,62 @@ +package org.nutz.json.meta; +public class IntellifUserAuthVo { + + private String xm; + private String photo; + private String photoBase; // base64 + private String fromCidId; + private String sjhm; + private String dsmc; + private String gmsfhm; + private String mzmc; + public String getXm() { + return xm; + } + public void setXm(String xm) { + this.xm = xm; + } + public String getPhoto() { + return photo; + } + public void setPhoto(String photo) { + this.photo = photo; + } + public String getPhotoBase() { + return photoBase; + } + public void setPhotoBase(String photoBase) { + this.photoBase = photoBase; + } + public String getFromCidId() { + return fromCidId; + } + public void setFromCidId(String fromCidId) { + this.fromCidId = fromCidId; + } + public String getSjhm() { + return sjhm; + } + public void setSjhm(String sjhm) { + this.sjhm = sjhm; + } + public String getDsmc() { + return dsmc; + } + public void setDsmc(String dsmc) { + this.dsmc = dsmc; + } + public String getGmsfhm() { + return gmsfhm; + } + public void setGmsfhm(String gmsfhm) { + this.gmsfhm = gmsfhm; + } + public String getMzmc() { + return mzmc; + } + public void setMzmc(String mzmc) { + this.mzmc = mzmc; + } + +} + \ No newline at end of file diff --git a/test/org/nutz/json/meta/PojoWithLocalDateTime.java b/test/org/nutz/json/meta/PojoWithLocalDateTime.java new file mode 100644 index 0000000000..5d00b79973 --- /dev/null +++ b/test/org/nutz/json/meta/PojoWithLocalDateTime.java @@ -0,0 +1,8 @@ +package org.nutz.json.meta; + +import java.time.LocalDateTime; + +public class PojoWithLocalDateTime { + + public LocalDateTime localdt; +} diff --git a/test/org/nutz/json/pojo123.json b/test/org/nutz/json/pojo123.json new file mode 100644 index 0000000000..05a0117302 --- /dev/null +++ b/test/org/nutz/json/pojo123.json @@ -0,0 +1 @@ +{"data":[{"created":1603336724000,"updated":1603336724000,"id":"10785697","fromImageId":"0","fromCidId":"10785696","imageData":"http://44.80.20.118/download/image/2020/10/22/2020-10-22-11-18-44-074-3357.jpg","xm":"cenzhizhi","score":0.0,"type":4,"cid":null,"realName":"","gender":"","time":1603336724000,"age":"-1"}],"errCode":0,"maxPage":0,"total":1,"count":0} \ No newline at end of file diff --git a/test/org/nutz/lang/AllLang.java b/test/org/nutz/lang/AllLang.java index 42022954c4..b6b5fee8b3 100644 --- a/test/org/nutz/lang/AllLang.java +++ b/test/org/nutz/lang/AllLang.java @@ -31,5 +31,6 @@ AllMeta.class, CodeTest.class, AllEncrypts.class, - XmlsTest.class}) + XmlsTest.class, + SocketsTest.class}) public class AllLang {} diff --git a/test/org/nutz/lang/LangTest.java b/test/org/nutz/lang/LangTest.java index e507b185cc..f1c3b4b18e 100644 --- a/test/org/nutz/lang/LangTest.java +++ b/test/org/nutz/lang/LangTest.java @@ -58,6 +58,13 @@ public void test_map_to_obj() { assertEquals("aa", t0.getName()); assertEquals("bb", t0.getT1().getName()); } + + @Test + public void test_map_to_obj_c2s() { + Map map = Lang.map("{name:['aa']}"); + ObjT0 t0 = Lang.map2Object(map, ObjT0.class); + assertEquals("[\"aa\"]", t0.getName()); + } @Test public void test_equals_simple() { diff --git a/test/org/nutz/lang/MirrorTest.java b/test/org/nutz/lang/MirrorTest.java index 8a22402d49..a620b20392 100644 --- a/test/org/nutz/lang/MirrorTest.java +++ b/test/org/nutz/lang/MirrorTest.java @@ -11,12 +11,14 @@ import java.util.List; import java.util.Map; +import org.junit.Assert; import org.junit.Test; import org.nutz.NutzEnum; import org.nutz.dao.DB; import org.nutz.dao.entity.annotation.Id; import org.nutz.dao.entity.annotation.Name; import org.nutz.dao.test.meta.Pet; +import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.lang.born.Borning; import org.nutz.lang.meta.Email; import org.nutz.lang.meta.Issue392Bean; @@ -160,7 +162,7 @@ public void testCanCastToDirectly() { } @Test - public void testGetWrpperClass() { + public void testGetWrapperClass() { assertEquals(Boolean.class, Mirror.me(Boolean.class).getWrapperClass()); assertEquals(Boolean.class, Mirror.me(boolean.class).getWrapperClass()); assertEquals(Integer.class, Mirror.me(Integer.class).getWrapperClass()); @@ -268,14 +270,14 @@ public DS(int id, String... values) { } @Test - public void testBornByStaticDynamiceArgs() { + public void testBornByStaticDynamicArgs() { DS ds = Mirror.me(DS.class).born(23, new String[]{"TT", "FF"}); assertEquals(23, ds.id); assertEquals("FF", ds.values[1]); } @Test - public void testBornByStaticNullDynamiceArgs() { + public void testBornByStaticNullDynamicArgs() { DS ds = Mirror.me(DS.class).born(23); assertEquals(23, ds.id); assertEquals(0, ds.values.length); @@ -292,21 +294,21 @@ public DD(int id, String... values) { } @Test - public void testBornByInnerDynamiceArgs() { + public void testBornByInnerDynamicArgs() { DD ds = Mirror.me(DD.class).born(23, new String[]{"TT", "FF"}); assertEquals(23, ds.id); assertEquals("FF", ds.values[1]); } @Test - public void testBornByInnerNullDynamiceArgs() { + public void testBornByInnerNullDynamicArgs() { DD ds = Mirror.me(DD.class).born(23); assertEquals(23, ds.id); assertEquals(0, ds.values.length); } @Test - public void testBornByInnerOuterDynamiceArgs() { + public void testBornByInnerOuterDynamicArgs() { DD ds = Mirror.me(DD.class).born(23); assertEquals(23, ds.id); assertEquals(0, ds.values.length); @@ -539,7 +541,7 @@ public TBOC(DB db) { } @Test - public void test_borning_of_constractor() { + public void test_borning_of_constructor() { Borning b = Mirror.me(TBOC.class).getBorning("H2"); TBOC tb = b.born("H2"); assertEquals(DB.H2, tb.db); @@ -644,4 +646,21 @@ public void test_var_string_factory() throws NoSuchMethodException { Mirror.me(IssueVarStringMethodC.class).born(args.toArray()); Mirror.me(IssueVarStringMethodC.class).findMethod("make", args.toArray(new String[0])); } + + @Test + public void test_annotation_NPE() throws Exception { + + + IocBean annotation = Mirror.me(test.class).getAnnotation(IocBean.class); + + Assert.assertNull(annotation); + + } + + + public @interface test { + + } + + } diff --git a/test/org/nutz/lang/SocketsTest.java b/test/org/nutz/lang/SocketsTest.java new file mode 100644 index 0000000000..7d639866d3 --- /dev/null +++ b/test/org/nutz/lang/SocketsTest.java @@ -0,0 +1,33 @@ +package org.nutz.lang; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.Test; +import org.nutz.lang.socket.SocketAction; +import org.nutz.lang.socket.SocketContext; +import org.nutz.lang.socket.Sockets; + +public class SocketsTest { + + @Test + public void test_listen_and_send() { + final int port = 9081; + final Map actions = new HashMap(); + actions.put("ABC", new SocketAction() { + public void run(SocketContext context) { + context.writeLine("DEF"); + context.closeConn(); + } + }); + actions.put("close", Sockets.doClose()); + new Thread() { + public void run() { + Sockets.localListenByLine(port, actions); + }; + }.start(); + Lang.quiteSleep(1000); + System.out.println(Sockets.sendText("127.0.0.1", port, "ABC\r\n")); + System.out.println(Sockets.sendText("127.0.0.1", port, "close\r\n")); + } +} diff --git a/test/org/nutz/lang/StringsTest.java b/test/org/nutz/lang/StringsTest.java index 3b6532ea54..c3a0b2f9cd 100644 --- a/test/org/nutz/lang/StringsTest.java +++ b/test/org/nutz/lang/StringsTest.java @@ -432,6 +432,8 @@ public void test_is_email() { assertTrue(Strings.isEmail("mc02cxj@gmail.com")); assertTrue(Strings.isEmail("mc02cxj@sina.com.cn")); assertTrue(Strings.isEmail("mc02cxj.test@sina.com.cn")); + assertTrue(Strings.isEmail("ab1-23@1a.2b.3c.com")); + assertTrue(Strings.isEmail("xiaobai2-wu12.ji42@a1.b2.com")); Strings.isEmail(null); } diff --git a/test/org/nutz/lang/TimesTest.java b/test/org/nutz/lang/TimesTest.java index a460b9dcdf..df203c819a 100644 --- a/test/org/nutz/lang/TimesTest.java +++ b/test/org/nutz/lang/TimesTest.java @@ -151,4 +151,17 @@ public void long_long_time() throws ParseException { new SimpleDateFormat(fmt, new Locale("en")).parse(time); Times.parse(new SimpleDateFormat(fmt, new Locale("en")), time); } + + @Test + public void test_next_day() { + Date s = Times.nextDay(new Date(),1); + System.out.println(Times.format("yyyy-MM-dd HH:mm:ss",s)); + } + + + @Test + public void test_next_day2() { + long t = Times.ams("2019-01"); + System.out.println(new Date(t)); + } } diff --git a/test/org/nutz/lang/random/ArrayRandomTest.java b/test/org/nutz/lang/random/ArrayRandomTest.java index d6d0618a16..161b414fc3 100644 --- a/test/org/nutz/lang/random/ArrayRandomTest.java +++ b/test/org/nutz/lang/random/ArrayRandomTest.java @@ -1,12 +1,9 @@ package org.nutz.lang.random; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; import org.junit.Test; - import org.nutz.lang.Lang; -import org.nutz.lang.random.ArrayRandom; -import org.nutz.lang.random.Random; public class ArrayRandomTest { diff --git a/test/org/nutz/lang/random/EnumRandomTest.java b/test/org/nutz/lang/random/EnumRandomTest.java index b02868682e..3f642cad1b 100644 --- a/test/org/nutz/lang/random/EnumRandomTest.java +++ b/test/org/nutz/lang/random/EnumRandomTest.java @@ -1,6 +1,6 @@ package org.nutz.lang.random; -import static org.junit.Assert.*; +import static org.junit.Assert.assertTrue; import java.util.Arrays; import java.util.HashSet; diff --git a/test/org/nutz/lang/random/ListRandomTest.java b/test/org/nutz/lang/random/ListRandomTest.java index a6d152e9c4..8df6e50e69 100644 --- a/test/org/nutz/lang/random/ListRandomTest.java +++ b/test/org/nutz/lang/random/ListRandomTest.java @@ -1,12 +1,9 @@ package org.nutz.lang.random; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; import org.junit.Test; - import org.nutz.lang.Lang; -import org.nutz.lang.random.Random; -import org.nutz.lang.random.ListRandom; public class ListRandomTest { diff --git a/test/org/nutz/lang/random/StringGeneratorTest.java b/test/org/nutz/lang/random/StringGeneratorTest.java index beb879d050..7a86045660 100644 --- a/test/org/nutz/lang/random/StringGeneratorTest.java +++ b/test/org/nutz/lang/random/StringGeneratorTest.java @@ -1,6 +1,9 @@ package org.nutz.lang.random; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + import org.junit.Test; public class StringGeneratorTest { diff --git a/test/org/nutz/lang/reflect/FastClassFactoryTest.java b/test/org/nutz/lang/reflect/FastClassFactoryTest.java index 35521a66a3..549fd4266b 100644 --- a/test/org/nutz/lang/reflect/FastClassFactoryTest.java +++ b/test/org/nutz/lang/reflect/FastClassFactoryTest.java @@ -1,5 +1,8 @@ package org.nutz.lang.reflect; +import java.util.HashMap; +import java.util.Map; + import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -10,6 +13,7 @@ import org.nutz.dao.impl.NutDao; import org.nutz.dao.sql.Sql; import org.nutz.dao.test.meta.Pet; +import org.nutz.json.Json; import org.nutz.lang.Lang; import org.nutz.lang.Mirror; import org.nutz.lang.Stopwatch; @@ -120,6 +124,20 @@ public void test_fastclass_for_datasource() { fc = FastClassFactory.get(NutDao.class); fc.invoke(new NutDao(), "execute", new Class[]{Sql.class}, new Object[]{null}); } + + @Test + public void test_issue_1382() { + try { + Map map = new HashMap(); + map.put("a", 1); + map.put("b", 2); + map.put("c", 3); + System.out.println(Json.toJson(map.entrySet())); + } + finally { + + } + } public static void main(String[] args) throws Exception { // ASMifier.main(new String[]{"target/classes/org/nutz/lang/reflect/SimpleFastClass.class"}); diff --git a/test/org/nutz/lang/segment/CharSegmentTest.java b/test/org/nutz/lang/segment/CharSegmentTest.java index b59b49901b..52e83defd5 100644 --- a/test/org/nutz/lang/segment/CharSegmentTest.java +++ b/test/org/nutz/lang/segment/CharSegmentTest.java @@ -1,12 +1,10 @@ package org.nutz.lang.segment; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import org.junit.Test; -import org.nutz.lang.segment.CharSegment; -import org.nutz.lang.segment.Segment; - public class CharSegmentTest { @Test public void testNormal() { diff --git a/test/org/nutz/lang/segment/SegmentsTest.java b/test/org/nutz/lang/segment/SegmentsTest.java index d1c81ce85c..824d7cdeb6 100644 --- a/test/org/nutz/lang/segment/SegmentsTest.java +++ b/test/org/nutz/lang/segment/SegmentsTest.java @@ -1,6 +1,6 @@ package org.nutz.lang.segment; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; import org.junit.Test; import org.nutz.lang.Lang; diff --git a/test/org/nutz/lang/tmpl/TmplTest.java b/test/org/nutz/lang/tmpl/TmplTest.java index 23729466eb..768a214337 100644 --- a/test/org/nutz/lang/tmpl/TmplTest.java +++ b/test/org/nutz/lang/tmpl/TmplTest.java @@ -11,6 +11,35 @@ public class TmplTest { + @Test + public void test_getOr() { + NutMap context = Lang.map("a", "AAA"); + assertEquals("AAA", Tmpl.exec("${b|a?-nil-}", context)); + } + + @Test + public void test_dft_true_false() { + NutMap context = Lang.map("a", true); + assertEquals("x", Tmpl.exec("${a}", context)); + assertEquals("", Tmpl.exec("${a}", context)); + assertEquals("y", Tmpl.exec("${a}", context)); + assertEquals("x", Tmpl.exec("${a}", context)); + } + + @Test + public void test_string_replace() { + NutMap context = Lang.map("path:' ~/a/b/c '"); + assertEquals("-a-b-c", + Tmpl.exec("${path<:@trim;@replace'/','-';@replace'~'>}", context, true)); + } + + @Test + public void test_string_mapping() { + NutMap context = Lang.map("fruit:'A'"); + assertEquals("Apple", Tmpl.exec("${fruit(::A=Apple,B=Banana,C=Cherry)}", context, true)); + assertEquals("Apple", Tmpl.exec("${fruit<::A=Apple,B=Banana,C=Cherry>}", context, true)); + } + @Test public void test_customized_a() { assertEquals("A100C", Tmpl.exec("A@C", "@", "<", ">", Lang.map("b:100"), true)); @@ -114,6 +143,9 @@ public void test_boolean() { assertEquals("false", Tmpl.exec("${v}", null)); assertEquals("false", Tmpl.exec("${v}", Lang.map("{}"))); + + assertEquals("yes", Tmpl.exec("${v}", Lang.map("v:true"))); + assertEquals("", Tmpl.exec("${v}", Lang.map("v:false"))); } } diff --git a/test/org/nutz/lang/util/AllUtil.java b/test/org/nutz/lang/util/AllUtil.java index 54574c4a25..081a9cb6a1 100644 --- a/test/org/nutz/lang/util/AllUtil.java +++ b/test/org/nutz/lang/util/AllUtil.java @@ -5,6 +5,7 @@ @RunWith(Suite.class) @Suite.SuiteClasses({LinkedArrayTest.class, + LinkedByteBufferTest.class, LinkedCharArrayTest.class, LinkedIntArrayTest.class, IntRangeTest.class, @@ -16,5 +17,6 @@ ContextTest.class, NutMapTest.class, RegionTest.class, - MultiLinePropertiesTest.class}) + MultiLinePropertiesTest.class, + ResidentStatusTest.class}) public class AllUtil {} diff --git a/test/org/nutz/lang/util/DisksTest.java b/test/org/nutz/lang/util/DisksTest.java index a97b329412..5fc56cab05 100644 --- a/test/org/nutz/lang/util/DisksTest.java +++ b/test/org/nutz/lang/util/DisksTest.java @@ -16,12 +16,12 @@ public void test_get_canonical_path() { assertEquals("B", Disks.getCanonicalPath("A/B/../../B")); assertEquals("B/A", Disks.getCanonicalPath("../B/A")); assertEquals("B/A", Disks.getCanonicalPath("../../B/A")); - + assertEquals("/a/c", Disks.getCanonicalPath("/a/b/../c")); assertEquals("/a/b/c", Disks.getCanonicalPath("/a/b/./c")); assertEquals("/a/b", Disks.getCanonicalPath("/a/b/c/..")); assertEquals("/a/b", Disks.getCanonicalPath("/a/b/c//..")); - assertEquals("/a/c", Disks.getCanonicalPath("/a/./c/")); + assertEquals("/a/c/", Disks.getCanonicalPath("/a/./c/")); } @Test @@ -37,6 +37,24 @@ public void test_get_relative_path() { path = Disks.getRelativePath("D:/uu.txt", "D:/abc.gif"); assertEquals("abc.gif", path); + + path = Disks.getRelativePath("/a/b/x.html", "/a/b/f.html"); + assertEquals("f.html", path); + + path = Disks.getRelativePath("/a/x.html", "/a/b/f.html"); + assertEquals("b/f.html", path); + + path = Disks.getRelativePath("/a/b/", "/a/b/f.html"); + assertEquals("f.html", path); + + path = Disks.getRelativePath("/a/b/x.html", "/a/b/f.html"); + assertEquals("f.html", path); + + path = Disks.getRelativePath("abc.html", "./"); + assertEquals("./", path); + + path = Disks.getRelativePath("abc.html", "./", "--"); + assertEquals("--", path); } @Test @@ -45,7 +63,7 @@ public void test_simple_relative_path() { File d2 = Files.findFile("org/nutz/json"); String path = Disks.getRelativePath(d1, d2); - assertEquals("../json", path); + assertEquals("../json/", path); d1 = Files.findFile("org/nutz/lang"); d2 = Files.findFile("org/nutz/lang"); @@ -57,13 +75,13 @@ public void test_simple_relative_path() { d2 = Files.findFile("org/nutz/lang/util"); path = Disks.getRelativePath(d1, d2); - assertEquals("util", path); + assertEquals("util/", path); d1 = Files.findFile("org/nutz/dao"); d2 = Files.findFile("org/nutz/lang/util"); path = Disks.getRelativePath(d1, d2); - assertEquals("../lang/util", path); + assertEquals("../lang/util/", path); } } diff --git a/test/org/nutz/lang/util/FloatRangeTest.java b/test/org/nutz/lang/util/FloatRangeTest.java index 43a72d4e2f..d0df7c91cb 100644 --- a/test/org/nutz/lang/util/FloatRangeTest.java +++ b/test/org/nutz/lang/util/FloatRangeTest.java @@ -29,4 +29,10 @@ public void testMakeSimple() { assertTrue(3f == r.getRight()); } + @Test + public void onTest() { + FloatRange r = FloatRange.make(".3 : .5"); + assertTrue(r.on(.3f)); + } + } diff --git a/test/org/nutz/lang/util/LinkedByteBufferTest.java b/test/org/nutz/lang/util/LinkedByteBufferTest.java new file mode 100644 index 0000000000..c8fd9e0176 --- /dev/null +++ b/test/org/nutz/lang/util/LinkedByteBufferTest.java @@ -0,0 +1,168 @@ +package org.nutz.lang.util; + +import static org.junit.Assert.*; + +import java.io.IOException; + +import org.junit.Test; +import org.nutz.lang.Lang; + +public class LinkedByteBufferTest { + + @Test + public void test_read_empty() throws IOException { + LinkedByteBuffer lba = new LinkedByteBuffer(3, 1); + byte[] bs = new byte[100]; + int len = lba.read(bs); + assertEquals(-1, len); + } + + @Test + public void test_str_get_set() throws IOException { + LinkedByteBuffer lba = new LinkedByteBuffer(3, 1); + lba.write("1234567890"); + + assertEquals('1', lba.get(0)); + assertEquals('2', lba.get(1)); + assertEquals('3', lba.get(2)); + assertEquals('4', lba.get(3)); + assertEquals('5', lba.get(4)); + assertEquals('6', lba.get(5)); + assertEquals('7', lba.get(6)); + assertEquals('8', lba.get(7)); + assertEquals('9', lba.get(8)); + assertEquals('0', lba.get(9)); + + lba.set(0, 'a'); + lba.set(3, 'b'); + lba.set(9, 'c'); + + assertEquals('a', lba.get(0)); + assertEquals('2', lba.get(1)); + assertEquals('3', lba.get(2)); + assertEquals('b', lba.get(3)); + assertEquals('5', lba.get(4)); + assertEquals('6', lba.get(5)); + assertEquals('7', lba.get(6)); + assertEquals('8', lba.get(7)); + assertEquals('9', lba.get(8)); + assertEquals('c', lba.get(9)); + + lba.set(-1, 'x'); + lba.set(-2, 'y'); + lba.set(-3, 'z'); + + assertEquals('a', lba.get(0)); + assertEquals('2', lba.get(1)); + assertEquals('3', lba.get(2)); + assertEquals('b', lba.get(3)); + assertEquals('5', lba.get(4)); + assertEquals('6', lba.get(5)); + assertEquals('7', lba.get(6)); + assertEquals('z', lba.get(7)); + assertEquals('y', lba.get(8)); + assertEquals('x', lba.get(9)); + + String str = lba.readAll(); + assertEquals("a23b567zyx", str); + assertNull(lba.readAll()); + } + + @Test + public void test_str_seek_read_write2() throws IOException { + LinkedByteBuffer lba = new LinkedByteBuffer(3, 1); + lba.write("1234567890"); + + String str = lba.readAll(); + assertEquals("1234567890", str); + + lba.seekRead(3); + str = lba.readAll(); + assertEquals("4567890", str); + + assertNull(lba.readAll()); + assertEquals(10, lba.getLimit()); + assertEquals(10, lba.getWriteIndex()); + + lba.seekWrite(2); + lba.write("abc"); + lba.seekRead(0); + lba.seekWrite(10); + + str = lba.readAll(); + assertEquals("12abc67890", str); + } + + @Test + public void test_str_seek_read_write() throws IOException { + LinkedByteBuffer lba = new LinkedByteBuffer(3, 1); + lba.write("123456789"); + + String str = lba.readAll(); + assertEquals("123456789", str); + + lba.seekRead(3); + str = lba.readAll(); + assertEquals("456789", str); + + assertNull(lba.readAll()); + assertEquals(9, lba.getLimit()); + assertEquals(9, lba.getWriteIndex()); + + lba.seekWrite(2); + lba.write("abc"); + lba.seekRead(0); + lba.seekWrite(9); + + str = lba.readAll(); + assertEquals("12abc6789", str); + } + + @Test + public void test_str_read_write() throws IOException { + LinkedByteBuffer lba = new LinkedByteBuffer(3, 1); + lba.write("1234567890"); + + String str = lba.readAll(); + assertEquals("1234567890", str); + + assertNull(lba.readAll()); + } + + @Test + public void test_str_write_readAll() throws IOException { + LinkedByteBuffer lba = new LinkedByteBuffer(3, 1); + lba.write("hello"); + lba.write(" world"); + + String str = lba.readAll(); + assertEquals("hello world", str); + + String sha1 = Lang.sha1(str); + assertEquals(sha1, lba.sha1sum()); + + assertNull(lba.readAll()); + assertNull(lba.readAll()); + assertNull(lba.readLine()); + assertNull(lba.readLine()); + } + + @Test + public void test_str_write_readLine() throws IOException { + LinkedByteBuffer lba = new LinkedByteBuffer(3, 1); + lba.write("hello"); + lba.write(" world"); + + String str = lba.readLine(); + assertEquals("hello world", str); + + String md5 = Lang.md5(str); + assertEquals(md5, lba.md5sum()); + + assertNull(lba.readLine()); + assertNull(lba.readLine()); + assertNull(lba.readAll()); + assertNull(lba.readAll()); + } + +} diff --git a/test/org/nutz/lang/util/NutMapTest.java b/test/org/nutz/lang/util/NutMapTest.java index 5aad58dc6d..d513a3e3c8 100644 --- a/test/org/nutz/lang/util/NutMapTest.java +++ b/test/org/nutz/lang/util/NutMapTest.java @@ -9,6 +9,8 @@ import org.junit.Ignore; import org.junit.Test; +import org.nutz.json.Json; +import org.nutz.json.JsonFormat; import org.nutz.lang.Lang; public class NutMapTest { @@ -92,4 +94,16 @@ public void test_add_nutmap2() { assertEquals("s1", sList3.get(0).getString("nm")); } + @Test + public void test_attach() { + NutMap nutMap = new NutMap(); + nutMap.setv( "key", "nutMap1"); + NutMap nutMap2 = new NutMap(); + nutMap2.setv( "key", "nutMap2"); + nutMap.attach( nutMap2); + + assertEquals( "nutMap1", nutMap.get("key")); + assertEquals( "{\"key\":\"nutMap1\"}", Json.toJson( nutMap, JsonFormat.compact())); + } + } diff --git a/test/org/nutz/lang/util/ResidentStatusTest.java b/test/org/nutz/lang/util/ResidentStatusTest.java new file mode 100644 index 0000000000..200268c77d --- /dev/null +++ b/test/org/nutz/lang/util/ResidentStatusTest.java @@ -0,0 +1,76 @@ +package org.nutz.lang.util; + +import org.junit.Test; +import org.nutz.json.Json; +import org.nutz.json.JsonShape; +import org.nutz.lang.Mirror; + +import junit.framework.Assert; + +public class ResidentStatusTest { + + + @JsonShape(JsonShape.Type.NAME) + public enum ResidentStatus { + + Init(){ + @Override + public void apply() { + throw new RuntimeException("resident.apply.init"); + } + }, + Confirm(){ + @Override + public void apply() { + throw new RuntimeException("resident.apply.exist"); + } + + @Override + public void confirmRoommate() { + throw new RuntimeException("resident.apply.confirm.conformed"); + + } + }, + Reject(){ + @Override + public void confirmRoommate() { + throw new RuntimeException("resident.apply.confirm.rejected"); + + } + }; + + + public void apply() { + + } + + public void confirmRoommate() { + + } + } + + @Test + public void test_IsEnum() throws NoSuchMethodException, SecurityException { + + ResidentStatus init = ResidentStatus.Init; + + Mirror me = Mirror.me(init); + + System.out.println(init.getClass().isEnum()); + + System.out.println(init.getClass()); + + System.out.println(init.getDeclaringClass().isEnum()); + + Assert.assertTrue(me.isEnum()); + + System.out.println(init.getClass().getSuperclass()); + + System.out.println(init.getDeclaringClass()); + + System.out.println(init.getDeclaringClass().getMethod("name")); + + System.out.println(Json.toJson(new NutMap("e", init))); + + } +} diff --git a/test/org/nutz/lang/util/XmlsTest.java b/test/org/nutz/lang/util/XmlsTest.java index 0b63f2a150..1f5ee59d5d 100644 --- a/test/org/nutz/lang/util/XmlsTest.java +++ b/test/org/nutz/lang/util/XmlsTest.java @@ -2,6 +2,7 @@ import java.io.ByteArrayInputStream; import java.io.InputStream; +import java.io.UnsupportedEncodingException; import java.util.Collection; import java.util.List; import java.util.Map; @@ -9,6 +10,7 @@ import org.junit.Assert; import org.junit.Test; import org.nutz.lang.Xmls; +import org.nutz.lang.Xmls.XmlParserOpts; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -53,4 +55,24 @@ public void test_dup_as_list() { Object pets = user.get("pet"); assertTrue(pets instanceof Collection || pets.getClass().isArray()); } + + @Test + public void test3() throws UnsupportedEncodingException { + NutMap data = NutMap.NEW(); + data.setv("aaa","111"); + data.setv("bbb","222"); + String oper = Xmls.mapToXml("person", data); + Document xml = Xmls.xml(new ByteArrayInputStream(oper.getBytes("UTF-8"))); + + Element root = xml.getDocumentElement(); + Element sign_ele = xml.createElement("ddd"); + sign_ele.setTextContent(" "); + root.appendChild(sign_ele); + NutMap re = Xmls.asMap(root); + re.addv("ddd", ""); + String dd = Xmls.mapToXml("test", re); + System.out.println(dd); + + assertEquals(dd, Xmls.mapToXml("test", Xmls.asMap(root, new XmlParserOpts(false, false, null, true)))); + } } diff --git a/test/org/nutz/log/Log4jTest.java b/test/org/nutz/log/Log4jTest.java index 3f0a12689e..3fc970fd6b 100644 --- a/test/org/nutz/log/Log4jTest.java +++ b/test/org/nutz/log/Log4jTest.java @@ -12,6 +12,7 @@ public class Log4jTest { @Test public void test_normal_debug() { + Logs.setAdapter(new Log4jLogAdapter()); Log log4nut = Logs.getLog(Dao.class); assertTrue(log4nut.getClass().getName().contains(Log4jLogAdapter.class.getName())); Logger log4j = LogManager.getLogger(Dao.class); diff --git a/test/org/nutz/mapl/D.java b/test/org/nutz/mapl/D.java new file mode 100644 index 0000000000..7c2c0d756f --- /dev/null +++ b/test/org/nutz/mapl/D.java @@ -0,0 +1,8 @@ +package org.nutz.mapl; + +public class D { + + String a; + int b; + boolean c; +} diff --git a/test/org/nutz/mapl/MaplTest.java b/test/org/nutz/mapl/MaplTest.java index 65cac86d9d..226bee0660 100644 --- a/test/org/nutz/mapl/MaplTest.java +++ b/test/org/nutz/mapl/MaplTest.java @@ -35,6 +35,31 @@ * @author juqkai(juqkai@gmail.com) */ public class MaplTest { + + @Test + public void test_obj_to_simple() { + D d = (D) Mapl.maplistToObj(Json.fromJson("{a: [1,2,3], b:\"9\", c: \"yes\"}") ,D.class); + assertEquals("[1, 2, 3]", d.a); + assertEquals(9, d.b); + assertEquals(true, d.c); + } + + /** + * Issue #1355 + */ + @Test + public void test_issue_1355() { + Object dest; + +// dest = Json.fromJson("{a: ['x',['A','B']]}"); +// assertEquals("x", Mapl.cell(dest, "'a[0]")); + + dest = Json.fromJson("{a: [[],['A','B']]}"); + assertEquals("A", Mapl.cell(dest, "'a[1][0]")); + + dest = Json.fromJson("{'a.b': {c:'ABC'}}"); + assertEquals("ABC", Mapl.cell(dest, "'a.b'.c")); + } /** * Issue #978 @@ -429,14 +454,15 @@ public void test_maplrebuild() { req.put("s2.s2[0]", "test"); System.out.println(Json.toJson(req.fetchNewobj())); } - + @Test public void test_complex_prefix() throws Exception { String params = "draw=1&columns%5B0%5D%5Bdata%5D=userId&columns%5B0%5D%5Bname%5D=&columns%5B0%5D%5Bsearchable%5D=true&columns%5B0%5D%5Borderable%5D=true&columns%5B0%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B0%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B1%5D%5Bdata%5D=loginname&columns%5B1%5D%5Bname%5D=&columns%5B1%5D%5Bsearchable%5D=true&columns%5B1%5D%5Borderable%5D=true&columns%5B1%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B1%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B2%5D%5Bdata%5D=nickname&columns%5B2%5D%5Bname%5D=&columns%5B2%5D%5Bsearchable%5D=true&columns%5B2%5D%5Borderable%5D=true&columns%5B2%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B2%5D%5Bsearch%5D%5Bregex%5D=false&order%5B0%5D%5Bcolumn%5D=0&order%5B0%5D%5Bdir%5D=asc&start=0&length=10&search%5Bvalue%5D=&search%5Bregex%5D=false"; - //String params = "columns%5B0%5D%5Bdata%5D=userId&columns%5B0%5D%5Bname%5D=&columns%5B0%5D%5Bsearchable%5D=true"; + // String params = + // "columns%5B0%5D%5Bdata%5D=userId&columns%5B0%5D%5Bname%5D=&columns%5B0%5D%5Bsearchable%5D=true"; NutMap map = new NutMap(); for (String kv : params.split("&")) { - //System.out.println(kv); + // System.out.println(kv); String[] tmp = kv.split("="); String key = URLDecoder.decode(tmp[0], "UTF-8"); String value = URLDecoder.decode(tmp.length > 1 ? tmp[1] : "", "UTF-8"); diff --git a/test/org/nutz/mvc/i18n/DemoLocalizationManager.java b/test/org/nutz/mvc/i18n/DemoLocalizationManager.java new file mode 100644 index 0000000000..a2b404f59c --- /dev/null +++ b/test/org/nutz/mvc/i18n/DemoLocalizationManager.java @@ -0,0 +1,48 @@ +package org.nutz.mvc.i18n; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.nutz.mvc.impl.NutMessageMap; + +/** + * LocalizationManager的参考实现. + * 可以在MainSetup.init方法内, 通过Mvcs.setLocalizationManager(ioc.get(MyLocalizationManager.class))设置默认实例. + * @author wendal + * + */ +public class DemoLocalizationManager implements LocalizationManager { + + protected String defaultLocal; + + protected Map msgs = new HashMap(); + + public void setDefaultLocal(String local) { + this.defaultLocal = local; + } + + public String getDefaultLocal() { + return defaultLocal; + } + + public Set getLocals() { + return msgs.keySet(); + } + + // 如果要动态替换msg, 例如从数据库读取 + // 请实现一个NutMessageMap的子类, 覆盖其get方法, 替换为动态实现 + public NutMessageMap getMessageMap(String local) { + return msgs.get(local); + } + + public String getMessage(String local, String key) { + NutMessageMap map = getMessageMap(local); + if (defaultLocal != null && map == null) { + map = getMessageMap(defaultLocal); + } + if (map == null) + return key; + return (String) map.getOrDefault(key, key); + } +} diff --git a/test/org/nutz/mvc/init/RestModuleTest.java b/test/org/nutz/mvc/init/RestModuleTest.java index 855a99c934..8dc80651df 100644 --- a/test/org/nutz/mvc/init/RestModuleTest.java +++ b/test/org/nutz/mvc/init/RestModuleTest.java @@ -1,11 +1,11 @@ package org.nutz.mvc.init; -import static org.junit.Assert.assertEquals; - import org.junit.Test; import org.nutz.mvc.AbstractMvcTest; import org.nutz.mvc.init.conf.RestModule; +import static org.junit.Assert.assertEquals; + public class RestModuleTest extends AbstractMvcTest { protected void initServletConfig() { @@ -89,4 +89,58 @@ public void test_pathArgs_01() throws Exception { String re = response.getAsString(); assertEquals("xyz?a=45&b=23", re); } + + @Test + public void test_abc_options() throws Exception { + request.setPathInfo("/abc"); + request.setMethod("Options"); + servlet.service(request, response); + String re = response.getAsString(); + assertEquals("options", re); + } + + @Test + public void test_abc_patch() throws Exception { + request.setPathInfo("/abc"); + request.setMethod("Patch"); + servlet.service(request, response); + String re = response.getAsString(); + assertEquals("patch", re); + } + + @Test + public void test_oag_options() throws Exception { + request.setPathInfo("/oag"); + request.setMethod("Options"); + servlet.service(request, response); + String re = response.getAsString(); + assertEquals("options&get", re); + } + + @Test + public void test_oag_get() throws Exception { + request.setPathInfo("/oag"); + request.setMethod("Get"); + servlet.service(request, response); + String re = response.getAsString(); + assertEquals("options&get", re); + } + + @Test + public void test_oap_options() throws Exception { + request.setPathInfo("/oap"); + request.setMethod("Options"); + servlet.service(request, response); + String re = response.getAsString(); + assertEquals("options&post", re); + } + + @Test + public void test_oap_post() throws Exception { + request.setPathInfo("/oap"); + request.setMethod("Post"); + servlet.service(request, response); + String re = response.getAsString(); + assertEquals("options&post", re); + } } diff --git a/test/org/nutz/mvc/init/conf/RestModule.java b/test/org/nutz/mvc/init/conf/RestModule.java index df63cf9c79..7f517adc94 100644 --- a/test/org/nutz/mvc/init/conf/RestModule.java +++ b/test/org/nutz/mvc/init/conf/RestModule.java @@ -1,6 +1,14 @@ package org.nutz.mvc.init.conf; -import org.nutz.mvc.annotation.*; +import org.nutz.mvc.annotation.At; +import org.nutz.mvc.annotation.DELETE; +import org.nutz.mvc.annotation.Fail; +import org.nutz.mvc.annotation.GET; +import org.nutz.mvc.annotation.OPTIONS; +import org.nutz.mvc.annotation.Ok; +import org.nutz.mvc.annotation.PATCH; +import org.nutz.mvc.annotation.POST; +import org.nutz.mvc.annotation.PUT; @Ok("raw") @Fail("json") @@ -42,4 +50,29 @@ public String pathArgs_01(int a, int b, String c) { return c + "?a=" + a + "&b=" + b; } + @At("/abc") + @OPTIONS + public String options() { + return "options"; + } + + @At("/abc") + @PATCH + public String patch() { + return "patch"; + } + + @At("/oag") + @GET + @OPTIONS + public String optionsAndGet() { + return "options&get"; + } + + @At("/oap") + @POST + @OPTIONS + public String optionsAndPost() { + return "options&post"; + } } diff --git a/test/org/nutz/mvc/init/module/MvcCommandLineRunner.java b/test/org/nutz/mvc/init/module/MvcCommandLineRunner.java new file mode 100644 index 0000000000..ea32bde8fd --- /dev/null +++ b/test/org/nutz/mvc/init/module/MvcCommandLineRunner.java @@ -0,0 +1,25 @@ +package org.nutz.mvc.init.module; + +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.mvc.CommandLineRunner; + +/** + * @author 黄川 huchuc@vip.qq.com + */ +@IocBean +public class MvcCommandLineRunner implements CommandLineRunner { + @Override + public void run() throws Exception { + System.out.println("call run Class CommandLineRunner"); + } + + @IocBean + public CommandLineRunner commandLineRunner(){ + return new CommandLineRunner(){ + @Override + public void run() throws Exception { + System.out.println("call run Method CommandLineRunner"); + } + }; + } +} diff --git a/test/org/nutz/mvc/testapp/adaptor/SimpleAdaptorTest.java b/test/org/nutz/mvc/testapp/adaptor/SimpleAdaptorTest.java index ebee6c14c4..61679d5da0 100644 --- a/test/org/nutz/mvc/testapp/adaptor/SimpleAdaptorTest.java +++ b/test/org/nutz/mvc/testapp/adaptor/SimpleAdaptorTest.java @@ -205,4 +205,12 @@ public void re_view_with_NutMap() { String str = resp.getContent(); assertEquals(Json.toJson(new NutMap("id", 1), JsonFormat.compact()), str); } + + @Test + public void test_localdt() { + resp = post("/adaptor/jdk8/localdt", new NutMap("date", "2018-02-20 21:11:51")); + assertEquals(200, resp.getStatus()); + String str = resp.getContent(); + assertEquals("2018-02-20T21:11:51", str); + } } diff --git a/test/org/nutz/mvc/testapp/classes/action/adaptor/AdaptorTestModule.java b/test/org/nutz/mvc/testapp/classes/action/adaptor/AdaptorTestModule.java index 1d793f11ae..1a28e93847 100644 --- a/test/org/nutz/mvc/testapp/classes/action/adaptor/AdaptorTestModule.java +++ b/test/org/nutz/mvc/testapp/classes/action/adaptor/AdaptorTestModule.java @@ -2,6 +2,7 @@ import java.io.InputStream; import java.io.Reader; +import java.time.LocalDateTime; import java.util.Date; import java.util.List; import java.util.Map; @@ -215,4 +216,11 @@ public String re_view_with_NutMap(@Param("..")NutMap map, ViewModel viewModel) { map.put("id", 2); // 如果走了NutMap的话,应该输出 {id:2} return "json"; } + + @Ok("raw") + @At("/jdk8/localdt") + public String localdatetime(@Param("date")LocalDateTime localDateTime) { + System.out.println(localDateTime); + return localDateTime.toString(); + } } diff --git a/test/org/nutz/mvc/testapp/classes/action/mapping/Issue1530MappingAction.java b/test/org/nutz/mvc/testapp/classes/action/mapping/Issue1530MappingAction.java new file mode 100644 index 0000000000..1c56e37f09 --- /dev/null +++ b/test/org/nutz/mvc/testapp/classes/action/mapping/Issue1530MappingAction.java @@ -0,0 +1,23 @@ +package org.nutz.mvc.testapp.classes.action.mapping; + +import org.nutz.mvc.annotation.ApiVersion; +import org.nutz.mvc.annotation.At; +import org.nutz.mvc.annotation.Ok; + +@Ok("raw") +@At("/mapping/issue1530/{version}") +@ApiVersion("v1") +public class Issue1530MappingAction { + + @ApiVersion("v1") + @At("/yourname/?") + public String getYourName(String name) { + return "v1-" + name; + } + + @ApiVersion("v2") + @At("/yourname/?") + public String getYourName2(int age) { + return "v2-" + age; + } +} \ No newline at end of file diff --git a/test/org/nutz/mvc/testapp/mapping/AllMapping.java b/test/org/nutz/mvc/testapp/mapping/AllMapping.java index 1de70e8903..eb8baed94a 100644 --- a/test/org/nutz/mvc/testapp/mapping/AllMapping.java +++ b/test/org/nutz/mvc/testapp/mapping/AllMapping.java @@ -5,7 +5,7 @@ import org.junit.runners.Suite.SuiteClasses; @RunWith(Suite.class) -@SuiteClasses({Issue1212MappingTest.class}) +@SuiteClasses({Issue1212MappingTest.class, Issue1530MappingTest.class}) public class AllMapping { } diff --git a/test/org/nutz/mvc/testapp/mapping/Issue1530MappingTest.java b/test/org/nutz/mvc/testapp/mapping/Issue1530MappingTest.java new file mode 100644 index 0000000000..ca14304220 --- /dev/null +++ b/test/org/nutz/mvc/testapp/mapping/Issue1530MappingTest.java @@ -0,0 +1,20 @@ +package org.nutz.mvc.testapp.mapping; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.nutz.mvc.testapp.BaseWebappTest; + +public class Issue1530MappingTest extends BaseWebappTest { + + @Test + public void test_issue_1530() { + get("/mapping/issue1530/v1/yourname/wendal"); + assertEquals(200, resp.getStatus()); + assertEquals("v1-wendal", resp.getContent()); + + get("/mapping/issue1530/v2/yourname/30"); + assertEquals(200, resp.getStatus()); + assertEquals("v2-30", resp.getContent()); + } +} diff --git a/test/org/nutz/runner/NutRunnerTest.java b/test/org/nutz/runner/NutRunnerTest.java new file mode 100644 index 0000000000..4a552503ec --- /dev/null +++ b/test/org/nutz/runner/NutRunnerTest.java @@ -0,0 +1,32 @@ +package org.nutz.runner; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * @Author: Haimming + * @Date: 2020-01-16 14:14 + * @Version 1.0 + */ +public class NutRunnerTest { + + @Test + public void run() { + TestRunner testRunner =new TestRunner("test"); + assertEquals(testRunner.getName(),"test"); + testRunner.setSleepAfterError(3); + assertTrue(testRunner.isRunning()); + testRunner.run(); + assertEquals(testRunner.getInterval(),1); + assertTrue(testRunner.isWaiting()); + assertTrue(testRunner.isAlive()); + testRunner.setDebug(true); + assertTrue(testRunner.isDebug()); + assertNotNull(testRunner.getUpAt()); + assertNotNull(testRunner.getDownAt()); + + + } + +} \ No newline at end of file diff --git a/test/org/nutz/runner/TestRunner.java b/test/org/nutz/runner/TestRunner.java new file mode 100644 index 0000000000..eb646459dc --- /dev/null +++ b/test/org/nutz/runner/TestRunner.java @@ -0,0 +1,25 @@ +package org.nutz.runner; + +/** + * @Author: Haimming + * @Date: 2020-01-16 14:16 + * @Version 1.0 + */ +public class TestRunner extends NutRunner { + /** + * 新建一个启动器 + * + * @param rname 本启动器的名称 + */ + public TestRunner(String rname) { + super(rname); + } + + public long exec() throws Exception{ + // do something + System.out.println("do something"); + getLock().stop(); + return 0; + } + +} diff --git a/test/org/nutz/trans/BatchTransTest.java b/test/org/nutz/trans/BatchTransTest.java index 4a8c345f22..f3bf97a3f7 100644 --- a/test/org/nutz/trans/BatchTransTest.java +++ b/test/org/nutz/trans/BatchTransTest.java @@ -1,12 +1,11 @@ package org.nutz.trans; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; import java.sql.Connection; import java.util.ArrayList; import org.junit.Test; - import org.nutz.dao.DaoException; import org.nutz.dao.TableName; import org.nutz.dao.test.DaoCase; diff --git a/test/org/nutz/trans/Cat.java b/test/org/nutz/trans/Cat.java index 5cb2176291..f446ad3506 100644 --- a/test/org/nutz/trans/Cat.java +++ b/test/org/nutz/trans/Cat.java @@ -1,6 +1,9 @@ package org.nutz.trans; -import org.nutz.dao.entity.annotation.*; +import org.nutz.dao.entity.annotation.Column; +import org.nutz.dao.entity.annotation.Id; +import org.nutz.dao.entity.annotation.Name; +import org.nutz.dao.entity.annotation.Table; @Table("trans_cat") public class Cat { diff --git a/test/org/nutz/trans/Company.java b/test/org/nutz/trans/Company.java index 2e32359b87..76e1bec7eb 100644 --- a/test/org/nutz/trans/Company.java +++ b/test/org/nutz/trans/Company.java @@ -1,6 +1,9 @@ package org.nutz.trans; -import org.nutz.dao.entity.annotation.*; +import org.nutz.dao.entity.annotation.Column; +import org.nutz.dao.entity.annotation.Id; +import org.nutz.dao.entity.annotation.Name; +import org.nutz.dao.entity.annotation.Table; @Table("trans_company") public class Company { diff --git a/test/org/nutz/trans/Master.java b/test/org/nutz/trans/Master.java index 608b8ba9b6..0da63ef24f 100644 --- a/test/org/nutz/trans/Master.java +++ b/test/org/nutz/trans/Master.java @@ -1,6 +1,9 @@ package org.nutz.trans; -import org.nutz.dao.entity.annotation.*; +import org.nutz.dao.entity.annotation.Column; +import org.nutz.dao.entity.annotation.Id; +import org.nutz.dao.entity.annotation.Name; +import org.nutz.dao.entity.annotation.Table; @Table("trans_master") public class Master { diff --git a/test/org/nutz/trans/SimpleTransTest.java b/test/org/nutz/trans/SimpleTransTest.java index 5c3cae0d8e..4d3e9cd6a4 100644 --- a/test/org/nutz/trans/SimpleTransTest.java +++ b/test/org/nutz/trans/SimpleTransTest.java @@ -1,6 +1,8 @@ package org.nutz.trans; -import static org.junit.Assert.*; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.sql.Connection; diff --git a/test/org/nutz/trans/TransLevelTest.java b/test/org/nutz/trans/TransLevelTest.java index e6bd4d24cf..07e7079b7f 100644 --- a/test/org/nutz/trans/TransLevelTest.java +++ b/test/org/nutz/trans/TransLevelTest.java @@ -1,6 +1,6 @@ package org.nutz.trans; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; import java.sql.Connection; import java.util.concurrent.Callable; @@ -8,7 +8,6 @@ import java.util.concurrent.Executors; import org.junit.Assert; - import org.junit.Test; import org.nutz.Nutzs; import org.nutz.dao.ConnCallback; diff --git a/test/org/nutz/trans/TransactionTest.java b/test/org/nutz/trans/TransactionTest.java index 6314a0211a..0ec388ca7b 100644 --- a/test/org/nutz/trans/TransactionTest.java +++ b/test/org/nutz/trans/TransactionTest.java @@ -1,14 +1,13 @@ package org.nutz.trans; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; import org.junit.Test; - import org.nutz.dao.test.DaoCase; import org.nutz.lang.Lang; import org.nutz.service.IdEntityService; -import org.nutz.trans.Atom; -import org.nutz.trans.Trans; public class TransactionTest extends DaoCase { diff --git a/test/org/nutz/validate/NutValidateTest.java b/test/org/nutz/validate/NutValidateTest.java new file mode 100644 index 0000000000..018f32c69d --- /dev/null +++ b/test/org/nutz/validate/NutValidateTest.java @@ -0,0 +1,68 @@ +package org.nutz.validate; + +import org.junit.Test; +import org.nutz.lang.util.NutMap; + +import static org.junit.Assert.*; + +/** + * @Author: Haimming + * @Date: 2020-01-16 11:40 + * @Version 1.0 + */ +public class NutValidateTest { + + @Test + public void addAll() { + NutValidate validate = new NutValidate(NutMap.NEW().addv("notNull", true)); + NutMap val = NutMap.NEW(); + val.addv("trim", true); + validate.addAll(val); + try { + String notNull = null; + validate.check(notNull); + } catch (NutValidateException e) { + assertNotNull(e); + } + + validate = new NutValidate(NutMap.NEW().addv("intRange", "(10,20]")); + try { + int intRange = 30; + validate.check(intRange); + } catch (NutValidateException e) { + assertNotNull(e); + } + //dateRange + validate = new NutValidate(NutMap.NEW().addv("dateRange", "(2018-12-02,2018-12-31]")); + try { + String dateRange = "2020-01-16"; + validate.check(dateRange); + } catch (NutValidateException e) { + assertNotNull(e); + } + //maxLength + validate = new NutValidate(NutMap.NEW().addv("maxLength", 5)); + try { + String maxLength = "2020-01-16"; + validate.check(maxLength); + } catch (NutValidateException e) { + assertNotNull(e); + } + validate = new NutValidate(NutMap.NEW().addv("minLength", 20)); + try { + String minLength = "2020-01-16"; + validate.check(minLength); + } catch (NutValidateException e) { + assertNotNull(e); + } + validate = new NutValidate(NutMap.NEW().addv("trim", true)); + try { + String trim = " abc ddc "; + validate.check(trim); + System.out.println(trim); + } catch (NutValidateException e) { + e.printStackTrace(); + assertNotNull(e); + } + } +} \ No newline at end of file diff --git a/test/tmpl.nutz-test.properties b/test/tmpl.nutz-test.properties index 079705d6c2..0dcf122912 100644 --- a/test/tmpl.nutz-test.properties +++ b/test/tmpl.nutz-test.properties @@ -1,9 +1,14 @@ -driver=com.mysql.jdbc.Driver +driver=com.mysql.cj.jdbc.Driver #driver=org.mariadb.jdbc.Driver -url=jdbc:mysql://127.0.0.1/nutztest?zeroDateTimeBehavior=convertToNull&serverTimezone=GMT%2b8 +url=jdbc:mysql://127.0.0.1:3306/nutztest?zeroDateTimeBehavior=convertToNull&serverTimezone=GMT%2b8 username=root password=root +#driver=com.yashandb.jdbc.Driver +#url=jdbc:yasdb://192.168.1.2:1688/yashan?connectTimeout=60&socketTimeout=120&loginTimeout=60 +#username=root +#password=root + #driver=oracle.jdbc.OracleDriver #url=jdbc:oracle:thin:@//127.0.0.1:1521/XE #username=system @@ -21,7 +26,7 @@ password=root #url=jdbc:h2:mem: #driver=org.postgresql.Driver -#url=jdbc:postgresql://127.0.0.1:5433/nutztest +#url=jdbc:postgresql://127.0.0.1:5432/nutztest #username=postgres #password=root #validationQuery=select 1 @@ -33,4 +38,4 @@ password=root #driver=org.sqlite.JDBC #url=jdbc:sqlite:nutz.db #username=root -#password=root \ No newline at end of file +#password=root diff --git a/tools/rb/nutztest/Gemfile.lock b/tools/rb/nutztest/Gemfile.lock index 4ec1d68d05..6a6448b31a 100644 --- a/tools/rb/nutztest/Gemfile.lock +++ b/tools/rb/nutztest/Gemfile.lock @@ -6,8 +6,8 @@ GEM eventmachine (1.2.0.1) eventmachine (1.2.0.1-x86-mingw32) multi_json (1.12.1) - rack (1.6.4) - rack-protection (1.5.3) + rack (1.6.12) + rack-protection (1.5.5) rack rack-test (0.6.3) rack (>= 1.0)