Repository: jiangguilong2000/gamioo Branch: master Commit: 0b3ff272d8d4 Files: 272 Total size: 709.3 KB Directory structure: gitextract_bng3r18m/ ├── .gitattributes ├── .github/ │ ├── FUNDING.yml │ └── workflows/ │ ├── gradle-publish.yml │ └── gradle.yml ├── .gitignore ├── .settings/ │ └── .gitignore ├── LICENSE ├── README.en-US.md ├── README.md ├── _config.yml ├── build.gradle ├── docs/ │ ├── _config.yml │ └── index.md ├── gamioo-cache/ │ ├── README.md │ ├── build.gradle │ └── src/ │ └── jmh/ │ └── java/ │ └── io/ │ └── gamioo/ │ └── cache/ │ └── CacheBenchMark.java ├── gamioo-common/ │ ├── build.gradle │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── io/ │ │ └── gamioo/ │ │ └── common/ │ │ ├── concurrent/ │ │ │ ├── Group.java │ │ │ └── NameableThreadFactory.java │ │ ├── constant/ │ │ │ ├── CacheConstant.java │ │ │ ├── Describe.java │ │ │ ├── ModuleConstant.java │ │ │ ├── SystemConstant.java │ │ │ └── TimeConstant.java │ │ ├── exception/ │ │ │ ├── BeansException.java │ │ │ ├── NestedIOException.java │ │ │ ├── NoPublicFieldException.java │ │ │ ├── NoPublicMethodException.java │ │ │ ├── ServerBootstrapException.java │ │ │ └── ServiceException.java │ │ ├── http/ │ │ │ └── RequestMethod.java │ │ ├── lang/ │ │ │ ├── Cache.java │ │ │ ├── Server.java │ │ │ └── ServerInfo.java │ │ ├── shape/ │ │ │ ├── AABB.java │ │ │ ├── Point.java │ │ │ ├── Sector.java │ │ │ └── Shape.java │ │ ├── util/ │ │ │ ├── AnnotationUtils.java │ │ │ ├── ArrayUtils.java │ │ │ ├── Assert.java │ │ │ ├── ByteArrayUtils.java │ │ │ ├── CharsetUtils.java │ │ │ ├── ClassUtils.java │ │ │ ├── FieldUtils.java │ │ │ ├── FileUtils.java │ │ │ ├── JSONUtils.java │ │ │ ├── JVMUtil.java │ │ │ ├── JsonXmlUtil.java │ │ │ ├── MathUtils.java │ │ │ ├── MethodUtils.java │ │ │ ├── NativeUtils.java │ │ │ ├── RandomUtils.java │ │ │ ├── ResourceUtils.java │ │ │ ├── StringUtils.java │ │ │ ├── TelnetUtils.java │ │ │ ├── ThreadUtils.java │ │ │ └── XMLUtil.java │ │ └── vector/ │ │ ├── Vector2f.java │ │ └── Vector3f.java │ └── test/ │ ├── java/ │ │ └── io/ │ │ └── gamioo/ │ │ ├── JsonXmlUtilTest.java │ │ └── MainT.java │ └── resources/ │ ├── gate-config.xml │ ├── junit-platform.properties │ ├── log4j2.component.properties │ └── log4j2.xml ├── gamioo-compress/ │ ├── README.md │ ├── build.gradle │ └── src/ │ ├── jmh/ │ │ ├── java/ │ │ │ └── io/ │ │ │ └── gamioo/ │ │ │ └── compress/ │ │ │ ├── CompressBenchMark.java │ │ │ └── SimilarityBenchMark.java │ │ └── resources/ │ │ ├── message.txt │ │ ├── mini.txt │ │ ├── readu.txt │ │ └── short.txt │ └── main/ │ ├── java/ │ │ └── io/ │ │ └── gamioo/ │ │ └── compress/ │ │ ├── Main.java │ │ └── ZlibUtil.java │ └── resources/ │ └── message.txt ├── gamioo-config/ │ ├── build.gradle │ └── src/ │ └── main/ │ └── java/ │ └── io/ │ └── gamioo/ │ └── config/ │ ├── NacosUtil.java │ ├── ServerConfig.java │ └── ServerConfigManager.java ├── gamioo-event/ │ ├── .settings/ │ │ └── .gitignore │ ├── build.gradle │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── io/ │ │ └── gamioo/ │ │ └── event/ │ │ ├── EventService.java │ │ └── package-info.java │ └── test/ │ ├── java/ │ │ └── io/ │ │ └── gamioo/ │ │ └── event/ │ │ └── guava/ │ │ ├── BaseEvent.java │ │ ├── MainT.java │ │ ├── StageEventHandler.java │ │ ├── UserEventHandler.java │ │ └── UserLoginEvent.java │ └── resources/ │ ├── log4j2.component.properties │ └── log4j2.xml ├── gamioo-game/ │ ├── .settings/ │ │ └── .gitignore │ ├── build.gradle │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── io/ │ │ └── gamioo/ │ │ └── game/ │ │ ├── Main.java │ │ ├── activity/ │ │ │ └── package-info.java │ │ └── word/ │ │ ├── DirtyWordUnit.java │ │ ├── DirtyWordsReader.java │ │ └── DirtyWordsValidator.java │ └── test/ │ ├── java/ │ │ └── io/ │ │ └── gamioo/ │ │ └── game/ │ │ ├── BalanceBusinessExecutorTest.java │ │ └── DeviationDTO.java │ └── resources/ │ ├── log4j2.component.properties │ └── log4j2.xml ├── gamioo-ioc/ │ ├── .settings/ │ │ └── .gitignore │ ├── build.gradle │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── io/ │ │ └── gamioo/ │ │ └── ioc/ │ │ ├── PropertyValue.java │ │ ├── PropertyValues.java │ │ ├── annotation/ │ │ │ ├── AnnotationAttributes.java │ │ │ ├── AnnotationBeanDefinitionReader.java │ │ │ ├── Attribute.java │ │ │ ├── Bean.java │ │ │ ├── CommandMapping.java │ │ │ ├── Configuration.java │ │ │ ├── DefaultResourceLoader.java │ │ │ ├── Mapping.java │ │ │ ├── RequestMapping.java │ │ │ ├── Scheduled.java │ │ │ ├── Subscribe.java │ │ │ └── Value.java │ │ ├── config/ │ │ │ ├── BeanFactoryPostProcessor.java │ │ │ ├── BeanPostProcessor.java │ │ │ └── BeanReference.java │ │ ├── context/ │ │ │ ├── ApplicationContext.java │ │ │ ├── ClassPathBeanDefinitionScanner.java │ │ │ └── ConfigApplicationContext.java │ │ ├── definition/ │ │ │ ├── BeanDefinition.java │ │ │ ├── ConfigurationBeanDefinition.java │ │ │ ├── ControllerBeanDefinition.java │ │ │ ├── Definition.java │ │ │ ├── FieldDefinition.java │ │ │ ├── GenericBeanDefinition.java │ │ │ ├── GenericFieldDefinition.java │ │ │ ├── GenericMethodDefinition.java │ │ │ ├── ListFieldDefinition.java │ │ │ ├── MapFieldDefinition.java │ │ │ ├── MethodDefinition.java │ │ │ └── ResourceBeanDefinition.java │ │ ├── factory/ │ │ │ ├── BeanFactory.java │ │ │ ├── ObjectFactory.java │ │ │ ├── annotation/ │ │ │ │ └── Autowired.java │ │ │ ├── support/ │ │ │ │ ├── AbstractAutowireCapableBeanFactory.java │ │ │ │ ├── AbstractBeanDefinition.java │ │ │ │ ├── AbstractBeanDefinitionReader.java │ │ │ │ ├── AbstractBeanFactory.java │ │ │ │ ├── BeanDefinitionReader.java │ │ │ │ └── DefaultListableBeanFactory.java │ │ │ └── xml/ │ │ │ ├── XmlBeanDefinitionReader.java │ │ │ └── XmlResourceLoader.java │ │ ├── io/ │ │ │ ├── AbstractResource.java │ │ │ ├── FileClassResource.java │ │ │ ├── JarClassResource.java │ │ │ ├── Resource.java │ │ │ ├── ResourceCallback.java │ │ │ ├── ResourceLoader.java │ │ │ └── UrlFileResource.java │ │ ├── stereotype/ │ │ │ ├── Component.java │ │ │ ├── Controller.java │ │ │ ├── Repository.java │ │ │ ├── Resource.java │ │ │ ├── Service.java │ │ │ └── package-info.java │ │ ├── type/ │ │ │ ├── AnnotatedTypeMetadata.java │ │ │ ├── AnnotationMetadata.java │ │ │ ├── ClassMetadata.java │ │ │ ├── MethodMetadata.java │ │ │ └── classreading/ │ │ │ ├── AnnotationMetadataReadingVisitor.java │ │ │ ├── ClassMetadataReadingVisitor.java │ │ │ └── MetadataReader.java │ │ └── wrapper/ │ │ ├── BeanWrapper.java │ │ ├── Command.java │ │ └── MethodWrapper.java │ └── test/ │ ├── java/ │ │ └── io/ │ │ └── gamioo/ │ │ └── ioc/ │ │ ├── DebugService.java │ │ ├── IOCFactoryTest.java │ │ ├── MapTest.java │ │ ├── RoleService.java │ │ ├── SkillService.java │ │ ├── XmlBeanFactoryTest.java │ │ ├── action/ │ │ │ └── UserController.java │ │ ├── entity/ │ │ │ ├── Cache.java │ │ │ ├── DB.java │ │ │ ├── Server.java │ │ │ └── ServerConfig.java │ │ ├── map/ │ │ │ ├── AddItemCommand.java │ │ │ ├── AddMoneyCommand.java │ │ │ ├── AddRmbCommand.java │ │ │ └── Command.java │ │ └── skill/ │ │ ├── AbstractSkill.java │ │ ├── SkillA.java │ │ └── SkillB.java │ └── resources/ │ ├── gate-config.xml │ ├── ioc.xml │ ├── junit-platform.properties │ ├── log4j2.component.properties │ └── log4j2.xml ├── gamioo-log/ │ ├── .settings/ │ │ └── .gitignore │ ├── build.gradle │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── io/ │ │ │ └── gamioo/ │ │ │ └── log/ │ │ │ ├── MainT.java │ │ │ └── package-info.java │ │ └── resources/ │ │ ├── log4j2.component.properties │ │ └── log4j2.xml │ └── test/ │ ├── java/ │ │ └── io/ │ │ └── gamioo/ │ │ └── log/ │ │ └── Main.java │ └── resources/ │ └── log4j2.xml ├── gamioo-navigation/ │ ├── README.md │ ├── build.gradle │ └── src/ │ ├── jmh/ │ │ └── java/ │ │ └── io/ │ │ └── gamioo/ │ │ └── nav/ │ │ └── NavEngineBenchMark.java │ ├── main/ │ │ └── java/ │ │ └── io/ │ │ └── gamioo/ │ │ └── nav/ │ │ ├── Easy3dNav.java │ │ ├── INav.java │ │ ├── Main.java │ │ └── NavEngine.java │ └── test/ │ └── java/ │ └── io/ │ └── gamioo/ │ └── nav/ │ └── NavEngineTest.java ├── gamioo-network/ │ ├── .settings/ │ │ └── .gitignore │ ├── build.gradle │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── io/ │ │ └── gamioo/ │ │ └── network/ │ │ ├── package-info.java │ │ └── util/ │ │ └── IPUtil.java │ └── test/ │ └── java/ │ └── io/ │ └── gamioo/ │ └── network/ │ └── MainT.java ├── gamioo-orm/ │ ├── .settings/ │ │ └── .gitignore │ ├── build.gradle │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── io/ │ │ └── gamioo/ │ │ └── orm/ │ │ ├── OrmService.java │ │ └── package-info.java │ └── test/ │ └── java/ │ └── io/ │ └── gamioo/ │ └── orm/ │ └── MainT.java ├── gamioo-protocol/ │ ├── .settings/ │ │ └── .gitignore │ ├── build.gradle │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── io/ │ │ └── gamioo/ │ │ └── protocol/ │ │ ├── ProtocolService.java │ │ └── package-info.java │ └── test/ │ └── java/ │ └── io/ │ └── gamioo/ │ └── protocol/ │ └── MainT.java ├── gamioo-redis/ │ ├── .settings/ │ │ └── .gitignore │ ├── build.gradle │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── io/ │ │ └── gamioo/ │ │ └── redis/ │ │ ├── Redis.java │ │ ├── RedisConstant.java │ │ └── zset/ │ │ ├── ZSetUtils.java │ │ ├── generic/ │ │ │ ├── Entry.java │ │ │ ├── GenericZSet.java │ │ │ ├── ScoreHandler.java │ │ │ ├── ScoreHandlers.java │ │ │ ├── ScoreRangeSpec.java │ │ │ └── ZScoreRangeSpec.java │ │ ├── long2object/ │ │ │ ├── Long2ObjectEntry.java │ │ │ └── Long2ObjectZSet.java │ │ └── object2long/ │ │ ├── LongScoreHandler.java │ │ ├── LongScoreHandlers.java │ │ ├── LongScoreRangeSpec.java │ │ ├── Object2LongEntry.java │ │ ├── Object2LongZSet.java │ │ └── ZLongScoreRangeSpec.java │ └── test/ │ └── java/ │ └── io/ │ └── gamioo/ │ └── redis/ │ └── zset/ │ ├── generic/ │ │ └── GenericZSetTest.java │ ├── long2object/ │ │ └── Long2ObjectZSetTest.java │ └── object2long/ │ └── Object2LongZSetTest.java ├── gamioo-sandbox/ │ ├── build.gradle │ └── src/ │ ├── jmh/ │ │ ├── java/ │ │ │ └── io/ │ │ │ └── gamioo/ │ │ │ └── sandbox/ │ │ │ ├── AsymmetricBenchMark.java │ │ │ ├── InterruptBenchmark.java │ │ │ ├── MapBenchMark.java │ │ │ ├── ProtoDeserializeBenchMark.java │ │ │ ├── ProtoSerializeBenchMark.java │ │ │ └── SymmetricBenchmark.java │ │ └── resources/ │ │ └── message.txt │ ├── main/ │ │ └── java/ │ │ └── io/ │ │ └── gamioo/ │ │ └── sandbox/ │ │ ├── HarmDTO.java │ │ ├── LoginGame_S2C_Msg.java │ │ ├── SerializingUtil.java │ │ ├── SkillCategory.java │ │ └── SkillFire_S2C_Msg.java │ └── test/ │ ├── java/ │ │ └── io/ │ │ └── gamioo/ │ │ └── sandbox/ │ │ ├── AnsibleTest.java │ │ ├── Base64Test.java │ │ ├── BitMapTest.java │ │ ├── CryptoTest.java │ │ └── ProtoTest.java │ └── resources/ │ └── message.txt ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── version.properties ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ # # https://help.github.com/articles/dealing-with-line-endings/ # # These are explicitly windows files and should use crlf *.bat text eol=crlf ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] ================================================ FILE: .github/workflows/gradle-publish.yml ================================================ # This workflow uses actions that are not certified by GitHub. # They are provided by a third-party and are governed by # separate terms of service, privacy policy, and support # documentation. # This workflow will build a package using Gradle and then publish it to GitHub packages when a release is created # For more information see: https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#Publishing-using-gradle name: Gradle Package on: release: types: [created] jobs: build: runs-on: ubuntu-latest permissions: contents: read packages: write steps: - name: Checkout Code uses: actions/checkout@v3.3.0 - name: Set up JDK 8 uses: actions/setup-java@v3.10.0 with: java-version: '8' distribution: 'temurin' server-id: github # Value of the distributionManagement/repository/id field of the pom.xml settings-path: ${{ github.workspace }} # location for the settings.xml file - name: Run chmod to make gradlew executable run: chmod +x ./gradlew - name: Build with Gradle uses: gradle/gradle-build-action@v2.4.0 with: arguments: build # The USERNAME and TOKEN need to correspond to the credentials environment variables used in # the publishing section of your build.gradle - name: Publish to GitHub Packages uses: gradle/gradle-build-action@v2.4.0 with: arguments: publish env: USERNAME: ${{ github.actor }} TOKEN: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .github/workflows/gradle.yml ================================================ # This workflow uses actions that are not certified by GitHub. # They are provided by a third-party and are governed by # separate terms of service, privacy policy, and support # documentation. # This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle name: build on: [ "push", "pull_request" ] jobs: build: runs-on: ubuntu-latest steps: - name: Checkout Code uses: actions/checkout@v3.3.0 - name: Set up JDK 8 uses: actions/setup-java@v3.10.0 with: java-version: '8' distribution: 'temurin' - name: Run chmod to make gradlew executable run: chmod +x ./gradlew - name: Clean with Gradle uses: gradle/gradle-build-action@v2.4.0 with: arguments: clean - name: Build with Gradle uses: gradle/gradle-build-action@v2.4.0 with: arguments: build --scan - name: Coverage with Gradle uses: gradle/gradle-build-action@v2.4.0 with: arguments: coverage - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v3.1.1 with: token: ${{ secrets.CODECOV_TOKEN }} finish: needs: build runs-on: ubuntu-latest if: ${{success()}} steps: - run: echo "Build successfully on branch ${{ github.actor }}" ================================================ FILE: .gitignore ================================================ # Ignore Gradle project-specific cache directory .gradle # Ignore Gradle build output directory build /.project /gamioo-log/.project /gamioo-log/.classpath /gamioo-ioc/.project /gamioo-ioc/.classpath /gamioo-game/.classpath /gamioo-game/.project /gamioo-compress/.classpath /gamioo-compress/.project /gamioo-compress/.settings/*.prefs /gamioo-cache/.classpath /gamioo-cache/.project /gamioo-cache/.settings/org.eclipse.buildship.core.prefs /测试结果 -BeanFactoryTest.html /gamioo-beans/.settings/*.prefs /.idea /gamioo-beans/.project /gamioo-beans/.classpath /gamioo-pomelo/out/production/classes/com/zvidia/pomelo/exception/*.class /gamioo-pomelo/out/production/classes/com/zvidia/pomelo/protobuf/*.class /gamioo-pomelo/out/production/classes/com/zvidia/pomelo/protocol/*.class /gamioo-pomelo/out/production/classes/com/zvidia/pomelo/utils/*.class /gamioo-pomelo/out/production/classes/com/zvidia/pomelo/websocket/*.class /gamioo-pomelo/out/production/classes/io/gamioo/pomelo/*.class /gamioo-pomelo/out/production/classes/org/java_websocket/drafts/*.class /gamioo-pomelo/out/production/resources/log4j2.component.properties /gamioo-pomelo/out/production/resources/log4j2.xml /gamioo-sandbox/src/jmh/generated ================================================ FILE: .settings/.gitignore ================================================ /org.eclipse.buildship.core.prefs ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.en-US.md ================================================

Game , so easy.

github star

##### Language: [中文](README.md) | English `gamioo`, as you see, It is a Game server framework, based on this framework, you can quickly implement a highly available, easy to maintain, stable and high-performance game server. ## Features 1. **one**: Has integrated more than a dozen third-party platforms. 2. **two**: The minimalist design is very simple to use. ## Quick start ### Add maven dependency - Add JustAuth dependency These artifacts are available from Maven Central: ```xml io.gamioo gamioo-all {latest-version} ``` - Add http dependency(Only need one) > If there is already in the project, please ignore it. In addition, you need to pay special attention. If the low version of the dependency has been introduced in the project, please exclude the low > version of the dependency first, and then introduce the high version or the latest version of the dependency ### Using Gamioo API #### Simple ```java ``` ## Contributions 1. Fork this project to your repository 2. Clone the project after fork. 3. Modify the code (either to fix issue, or to add new features) 4. Commit and push code to a remote repository 5. Create a new PR (pull request), and select `dev` branch 6. Waiting for author to merge I look forward to your joining us. ## Contributors [contributors](https://gamioo.wiki/contributors.html) ## Change Logs [CHANGELOGS](https://gamioo.wiki/update.html) ## Recommend ## References ================================================ FILE: README.md ================================================

Game , so easy.

github star

##### 语言: 中文 | [English](README.en-US.md) ## 📌 简介 Game server framework, based on this framework, you can quickly implement a highly available, easy to maintain, stable and high-performance game server. ## 🔧 功能特点 ## 📄文件结构 ## TODO list ================================================ FILE: _config.yml ================================================ theme: jekyll-theme-architect ================================================ FILE: build.gradle ================================================ def releaseTime; def times; ext { Properties properties = new Properties() String fileName = "version.properties" File propertyFile = new File(rootDir.getAbsoluteFile(), fileName) properties.load(propertyFile.newDataInputStream()) String lastReleaseTime = properties["date"]; times = Integer.parseInt(properties["release"]); releaseTime = new Date().format("yyyyMMdd", TimeZone.getTimeZone("GMT+08:00")); if (lastReleaseTime != releaseTime) { properties["date"] = releaseTime; times = 1; } else { times++; } properties["release"] = String.valueOf(times); properties.store(propertyFile.newDataOutputStream(), "version info"); } buildscript { repositories { maven { url = uri("https://plugins.gradle.org/m2/") } } dependencies { classpath("me.champeau.jmh:jmh-gradle-plugin:0.6.8") } } subprojects { apply plugin: 'java-library'; apply plugin: 'maven-publish'; apply plugin: 'signing'; apply plugin: 'jacoco'; apply plugin: "me.champeau.jmh"; group = 'io.gamioo'; version = '0.2.14'; // version = '0.0.1-SNAPSHOT'; ext { releaseRoot = '../../dist' } sourceCompatibility = 11; targetCompatibility = 11; compileJava { options.encoding = 'UTF-8' options.compilerArgs << '-XDignore.symbol.file' options.fork = true options.forkOptions.executable = 'javac' } [compileJava, compileTestJava]*.options*.encoding = 'UTF-8'; tasks.withType(JavaCompile) { options.encoding = 'UTF-8' } tasks.withType(Test) { systemProperty "project.name", project.name; } /** * 定义资源集 * main源码包 * test单测包 * jmh基准测试包 */ sourceSets { main test jmh } /**创建java和resource目录*/ task createDirs { // println project.name + ":createDirs" sourceSets*.java.srcDirs*.each { it.mkdirs() } sourceSets*.resources.srcDirs*.each { it.mkdirs() } } jar { enabled = true archiveFileName = "${archiveBaseName.get()}.${archiveExtension.get()}"; manifest.attributes provider: 'Allen Jiang'; manifest.attributes["Created-By"] = "${System.getProperty("java.version")} (${System.getProperty("java.specification.vendor")})"; manifest.attributes["Implementation-Title"] = project.name; manifest.attributes["Implementation-Version"] = 'git rev-parse --abbrev-ref HEAD'.execute([], project.rootDir).text.trim() + "-" + 'git rev-parse --short HEAD'.execute([], project.rootDir).text.trim() + "-" + 'git rev-list HEAD --count'.execute([], project.rootDir).text.trim() + "-S" + releaseTime + "R" + times; } javadoc { options { encoding "UTF-8"; charSet 'UTF-8'; author true; version true; header = project.name; links "http://docs.oracle.com/javase/8/docs/api/"; } } task sourcesJar(type: Jar) { archiveClassifier = 'sources'; from sourceSets.main.allJava; } task javadocJar(type: Jar) { from javadoc; archiveClassifier = 'javadoc'; } // task generatePomFileForMavenCustomPublication { // destination = file("$buildDir/poms/pom.xml"); // } // build.dependsOn(generatePomFileForMavenCustomPublication); artifacts { archives jar; archives sourcesJar; archives javadocJar; } publishing { repositories { maven { if (project.version.endsWith('-SNAPSHOT')) { //快照版本的nexus仓库地址 name 'snapshots'; url 'https://oss.sonatype.org/content/repositories/snapshots/'; } else { name 'releases'; url 'https://oss.sonatype.org/service/local/staging/deploy/maven2/'; } credentials { username = System.getenv("NEXUS_USERNAME"); password = System.getenv("NEXUS_PASSWORD"); } } } publications { mavenJava(MavenPublication) { groupId project.group; artifactId project.name; version project.version; //若是war包,就写components.web,若是jar包,就写components.java from components.java; artifact sourcesJar; artifact javadocJar; suppressAllPomMetadataWarnings();//屏蔽所有编译时的WARNING pom { name = project.name; description = 'This is a game server framework.'; url = 'https://gamioo.io'; packaging = 'jar'; scm { connection = 'scm:git@github.com:jiangguilong2000/gamioo.git'; developerConnection = 'scm:git@github.com:jiangguilong2000/gamioo.git'; url = 'https://github.com/jiangguilong2000/gamioo/'; } licenses { license { name = 'Apache-2.0 License'; url = 'https://github.com/jiangguilong2000/gamioo/blob/master/LICENSE'; distribution = 'repo'; } } developers { developer { id = 'jgl2000'; name = '阿龙'; email = '41157121@qq.com'; url = 'https://blog.gamioo.io'; } } } // pom.withXml { // asNode().appendNode('description','A demonstration of maven POM customization'); // } } } } ext."signing.keyId" = System.getenv('SIGNING_KEYID') ext."signing.password" = System.getenv('SIGNING_PASSWORD') ext."signing.secretKeyRingFile" = System.getenv('SIGNING_SECRETKEYRINGFILE') signing { //sign configurations.archives; sign publishing.publications.mavenJava; } dependencies { testImplementation 'org.junit.jupiter:junit-jupiter:5.9.3' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.3' testImplementation group: 'org.mockito', name: 'mockito-core', version: '5.4.0' testImplementation group: 'org.mockito', name: 'mockito-junit-jupiter', version: '5.4.0' // 保证jmh目录下java代码能顺利import其他main目录下等代码 jmhImplementation project jmhImplementation 'org.openjdk.jmh:jmh-core:1.36' jmhAnnotationProcessor 'org.openjdk.jmh:jmh-generator-annprocess:1.36' // compileOnly group: 'org.apache.skywalking', name: 'apm-agent-core', version: '8.11.0' implementation group: 'org.jetbrains', name: 'annotations', version: '23.0.0' } jmh { // zip64 = false; jvmArgs = ['-Dfile.encoding=UTF-8']; humanOutputFile = project.file("${project.buildDir}/reports/jmh/human.txt"); // // // humanOutputFile.encoding = utf - 8; // // human-readable output file resultsFile = project.file("${project.buildDir}/reports/jmh/results.json") // results file resultFormat="JSON"; } test { useJUnitPlatform() maxParallelForks(1) ignoreFailures(false) failFast(true) afterSuite { desc, result -> if (!desc.parent) { def output = "Results: ${result.resultType} (${result.testCount} tests, ${result.successfulTestCount} passed, ${result.failedTestCount} failed, ${result.skippedTestCount} skipped)" def startItem = '| ', endItem = ' |' def repeatLength = startItem.length() + output.length() + endItem.length() println('\n' + ('-' * repeatLength) + '\n' + startItem + output + endItem + '\n' + ('-' * repeatLength)) } } reports.html.enabled = false reports.junitXml.enabled = false } task coverage(type: JacocoReport) { executionData fileTree(project.rootDir.absolutePath).include("**/build/jacoco/*.exec") sourceSets sourceSets.main, sourceSets.jmh } coverage.dependsOn { project.test } jacocoTestReport { reports { xml.enabled true html.enabled false } } check.dependsOn jacocoTestReport } allprojects { // task printSigning { // println(project.findProperty('signing.secretKeyRingFile')) // } // // task currentDir { // println file('.') // } repositories { //腾讯云的国内镜像 maven { url 'https://mirrors.cloud.tencent.com/nexus/repository/maven-public' } // maven { url 'https://s01.oss.sonatype.org/content/repositories/snapshots' } } } ================================================ FILE: docs/_config.yml ================================================ theme: jekyll-theme-architect ================================================ FILE: docs/index.md ================================================

## Welcome to GitHub Pages You can use the [editor on GitHub](https://github.com/jiangguilong2000/gamioo/edit/master/docs/index.md) to maintain and preview the content for your website in Markdown files. Whenever you commit to this repository, GitHub Pages will run [Jekyll](https://jekyllrb.com/) to rebuild the pages in your site, from the content in your Markdown files. ### Markdown Markdown is a lightweight and easy-to-use syntax for styling your writing. It includes conventions for ```markdown Syntax highlighted code block # Header 1 ## Header 2 ### Header 3 - Bulleted - List 1. Numbered 2. List **Bold** and _Italic_ and `Code` text [Link](url) and ![Image](src) ``` For more details see [GitHub Flavored Markdown](https://guides.github.com/features/mastering-markdown/). ### Jekyll Themes Your Pages site will use the layout and styles from the Jekyll theme you have selected in your [repository settings](https://github.com/jiangguilong2000/gamioo/settings). The name of this theme is saved in the Jekyll `_config.yml` configuration file. ### Support or Contact Having trouble with Pages? Check out our [documentation](https://docs.github.com/categories/github-pages-basics/) or [contact support](https://github.com/contact) and we’ll help you sort it out. ================================================ FILE: gamioo-cache/README.md ================================================

Cache, so easy.

github star

# 简介 📌 压缩相关 * 压缩算法 * caffeine * guava * 如何使用 ```bash implementation group: 'io.gamioo', name: 'gamioo-cache', version: '0.2.11' ``` #### 📄 性能测试结果如下: ```log Benchmark (type) Mode Cnt Score Error Units CacheBenchMark.cache guava thrpt 5 8797574.523 ± 11079456.005 ops/s CacheBenchMark.cache:get guava thrpt 5 7884606.600 ± 10841319.317 ops/s CacheBenchMark.cache:put guava thrpt 5 912967.923 ± 1076468.236 ops/s CacheBenchMark.cache caffeine thrpt 5 12313264.433 ± 24041374.853 ops/s CacheBenchMark.cache:get caffeine thrpt 5 9645145.413 ± 17628648.683 ops/s CacheBenchMark.cache:put caffeine thrpt 5 2668119.020 ± 7444828.382 ops/s ``` 在Windows下(4核8线程 Intel Core i7),5根线程读操作,5根线程写操作,很明显, - 存入API,caffeine 比 guava 性能达到了 292.2%; - 获取API,caffeine 比 guava 性能达到了 122.3%; ### 依赖&参考 dependncy : jdk: ```bash OpenJDK Runtime Environment (Tencent Kona 8.0.12) (build 1.8.0_352-b1) OpenJDK 64-Bit Server VM (Tencent Kona 8.0.12) (build 25.352-b1, mixed mode, sharing) ``` ## TODO list ================================================ FILE: gamioo-cache/build.gradle ================================================ dependencies { api project(':gamioo-log'); api group: 'com.github.ben-manes.caffeine', name: 'caffeine', version: '2.8.5' api group: 'com.google.guava', name: 'guava', version: '31.1-jre' api group: 'org.apache.commons', name: 'commons-lang3', version: '3.11' } ================================================ FILE: gamioo-cache/src/jmh/java/io/gamioo/cache/CacheBenchMark.java ================================================ package io.gamioo.cache; import com.github.benmanes.caffeine.cache.Caffeine; import com.google.common.cache.CacheBuilder; import org.apache.commons.lang3.RandomUtils; import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import java.util.concurrent.TimeUnit; /** * 缓存测试 * * @author Allen Jiang */ @State(Scope.Group) @BenchmarkMode({Mode.Throughput}) @OutputTimeUnit(TimeUnit.SECONDS) @Warmup(iterations = 5, time = 1) @Measurement(iterations = 5, time = 1) @Fork(value = 1) public class CacheBenchMark { @Param({"guava", "caffeine"}) private String type; private com.google.common.cache.Cache guavaCache; private com.github.benmanes.caffeine.cache.Cache caffeineCache; private int key; @Setup(Level.Trial) public void init() { switch (type) { case "guava": this.guavaCache = CacheBuilder.newBuilder().build(); break; case "caffeine": this.caffeineCache = Caffeine.newBuilder().build(); break; default: throw new IllegalArgumentException("Illegal cache type."); } } @TearDown(Level.Trial) public void destroy() { this.guavaCache = null; this.caffeineCache = null; } @Group("cache") @GroupThreads() @Setup(Level.Invocation) public void prepare() { key = RandomUtils.nextInt(0, Integer.MAX_VALUE); } @Benchmark @Group("cache") @GroupThreads(5) public void put() { if (guavaCache != null) { guavaCache.put(key, key); } if (caffeineCache != null) { caffeineCache.put(key, key); } } @Benchmark @Group("cache") @GroupThreads(5) public Integer get() { if (guavaCache != null) { return guavaCache.getIfPresent(key); } if (caffeineCache != null) { return caffeineCache.getIfPresent(key); } return 0; } public static void main(String[] args) { Options opt = new OptionsBuilder() .include(CacheBenchMark.class.getSimpleName()) .build(); try { new Runner(opt).run(); } catch (RunnerException e) { e.printStackTrace(); } } } ================================================ FILE: gamioo-common/build.gradle ================================================ dependencies { api project(':gamioo-log'); api group: 'com.google.guava', name: 'guava', version: '31.1-jre'; api group: 'com.esotericsoftware', name: 'reflectasm', version: '1.11.9'; api group: 'org.apache.commons', name: 'commons-lang3', version: '3.11'; api group: 'commons-io', name: 'commons-io', version: '2.8.0'; api group: 'org.ow2.asm', name: 'asm', version: '8.0.1'; api group: 'commons-net', name: 'commons-net', version: '3.9.0'; api group: 'commons-codec', name: 'commons-codec', version: '1.15' api group: 'org.dom4j', name: 'dom4j', version: '2.1.3'; api group: 'com.alibaba.fastjson2', name: 'fastjson2', version: '2.0.37'; api group: 'org.apache.commons', name: 'commons-collections4', version: '4.4'; api group: 'javax.servlet', name: 'javax.servlet-api', version: '4.0.1'; // api group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.14.2' //任务调度 api group: 'it.sauronsoftware.cron4j', name: 'cron4j', version: '2.2.5'; // api group: 'com.jfinal', name: 'jfinal', version: '5.0.8'; api group: 'org.jctools', name: 'jctools-core', version: '3.3.0'; // http api group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.14'; api group: 'org.apache.httpcomponents', name: 'fluent-hc', version: '4.5.14'; api group: 'jakarta.annotation', name: 'jakarta.annotation-api', version: '1.3.5'; api group: 'jakarta.servlet', name: 'jakarta.servlet-api', version: '4.0.4'; api group: 'org.reflections', name: 'reflections', version: '0.10.2'; api group: 'cn.hutool', name: 'hutool-core', version: '5.8.18'; } ================================================ FILE: gamioo-common/src/main/java/io/gamioo/common/concurrent/Group.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.common.concurrent; /** * 执行线程组枚举类. *

* 线程调度规则:N个线程 处理M个队列,哪个线程空闲了就去处理有任务的队列 *

* * @author Allen Jiang * @since 1.0.0 */ public enum Group { /** * 以玩家ID划分的线程. *

* 可以理解为一个玩家一个线程
*/ ModuleThreadGroup, /** * 以模块划分的线程. *

* 可以理解为一个Controller一个线程
* 如:登录,世界聊天,公会,排行榜 */ NettyThreadGroup, /** * Netty本身的Work线程. *

* 心跳,或完全没有IO操作的逻辑,直接交给Netty的Work线程处理掉 */ PlayerThreadGroup, /** * 一种串行执行队列处理线程.只会有一根线程来处理, */ QueueThreadGroup; } ================================================ FILE: gamioo-common/src/main/java/io/gamioo/common/concurrent/NameableThreadFactory.java ================================================ package io.gamioo.common.concurrent; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; /** * 线程工厂,可以设置别名 * * @author Allen Jiang[41157121@qq.com] * @since 1.0 */ public class NameableThreadFactory implements ThreadFactory { private final String name; private final AtomicInteger threadCounter = new AtomicInteger(0); @Override public Thread newThread(Runnable runnable) { StringBuilder threadName = new StringBuilder(name); threadName.append("-").append(threadCounter.getAndIncrement()); Thread thread = new Thread(group, runnable, threadName.toString()); return thread; } final ThreadGroup group; public NameableThreadFactory(String name) { SecurityManager securitymanager = System.getSecurityManager(); this.group = securitymanager == null ? Thread.currentThread().getThreadGroup() : securitymanager.getThreadGroup(); this.name = name; } } ================================================ FILE: gamioo-common/src/main/java/io/gamioo/common/constant/CacheConstant.java ================================================ package io.gamioo.common.constant; /** * @author Allen Jiang */ public class CacheConstant { public static final String RECORD = "record"; } ================================================ FILE: gamioo-common/src/main/java/io/gamioo/common/constant/Describe.java ================================================ package io.gamioo.common.constant; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Describe { String name(); //是否展示出来 String description(); } ================================================ FILE: gamioo-common/src/main/java/io/gamioo/common/constant/ModuleConstant.java ================================================ package io.gamioo.common.constant; import org.apache.commons.collections4.CollectionUtils; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; /** * @author Allen Jiang */ public class ModuleConstant { @Describe(name = "common", description = "通用模块") public static final int COMMON = 0; @Describe(name = "user", description = "用户模块") public static final int USER = 1; @Describe(name = "building", description = "建筑模块") public static final int BUILDING = 2; @Describe(name = "item", description = "道具模块") public static final int ITEM = 3; private static List moduleList = new ArrayList<>(); /** * 获取模块列表 * * @return 返回模块列表 */ public static List getModuleList() { if (CollectionUtils.isEmpty(moduleList)) { Field[] list = ModuleConstant.class.getDeclaredFields(); for (Field e : list) { Describe describe = e.getAnnotation(Describe.class); if (describe != null) { moduleList.add(describe.name()); } } } return moduleList; } } ================================================ FILE: gamioo-common/src/main/java/io/gamioo/common/constant/SystemConstant.java ================================================ package io.gamioo.common.constant; /** * @author Allen Jiang */ public class SystemConstant { // 服务器类型 /** * 游戏服务器 */ public final static String SERVER_TYPE_GAME = "game"; /** * 全局服务器 */ public final static String SERVER_TYPE_GLOBAL = "global"; /** * 日志服务器 */ public final static String SERVER_TYPE_LOGGER = "logger"; /** * 充值服务器 */ public final static String SERVER_TYPE_CHARGE = "charge"; /** * 网关服务器 */ public final static String SERVER_TYPE_GATE = "gate"; /** * admin服务器 */ public final static String SERVER_TYPE_ADMIN = "admin"; /** * 目录服务器 */ public final static String SERVER_TYPE_DIRECTORY = "directory"; public static final String LOCALHOST = "127.0.0.1"; // public static String getConfig(String gameType) { // return gameType + "_config"; // } // 服务器状态 /** * 服务器处于正常运行状态 */ public final static int SERVER_STATUS_OPENING = 0; /** * 服务器处于运维维护状态,只能允许GM进来 */ public final static int SERVER_STATUS_MAINTAIN = 1; /** * 服务器处于停服状态 */ public final static int SERVER_STATUS_STOPED = 2; // 系统 /** * 系统ID */ public final static int SYSTEM_ID = 0; /** * 系统名 */ public final static String SYSTEM_NAME = "系统"; /** * 系统 */ public final static String NAME_SYS = "SYSTEM"; // 游戏状态 /** * 敬请期待 */ public final static int GAME_STATUS_PENDING = 0; /** * 正常对外 */ public final static int GAME_STATUS_OPENING = 1; /** * 正在维护 */ public final static int GAME_STATUS_MAINTAIN = 2; /** * 对20毫秒以下的处理值进行监控 */ public final static int THRESHOLD_DELAY = 20; /** * 服务器类型转化 * * @param type 服务器类型 * @return 返回服务器数字类型 */ public static int getServerType(String type) { switch (type) { case SERVER_TYPE_DIRECTORY: { return 1; } case SERVER_TYPE_GAME: { return 2; } default: { return 0; } } } } ================================================ FILE: gamioo-common/src/main/java/io/gamioo/common/constant/TimeConstant.java ================================================ package io.gamioo.common.constant; import java.util.Date; public class TimeConstant { /** * 2020-01-01 00:00:00 */ public static final long LONG_AGO = 1577808000000l; /** * 2020-01-01 00:00:00 */ public static final Date DATE_LONG_AGO = new Date(LONG_AGO); /** * 一周毫秒时间 */ public static final int ONE_WEEK_MS = 7 * 24 * 60 * 60 * 1000; public static void main(String[] args) { //68719476735 //System.out.println(2051193600000l-1577808000000l); //System.out.println(TimeUtil.parse("2035-01-01","yyyy-MM-dd").getTime()); } } ================================================ FILE: gamioo-common/src/main/java/io/gamioo/common/exception/BeansException.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.common.exception; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public abstract class BeansException extends ServiceException { } ================================================ FILE: gamioo-common/src/main/java/io/gamioo/common/exception/NestedIOException.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.common.exception; import java.io.IOException; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public class NestedIOException extends IOException { /** * Construct a {@code NestedIOException} with the specified detail message. * * @param msg the detail message */ public NestedIOException(String msg) { super(msg); } /** * Construct a {@code NestedIOException} with the specified detail message * and nested exception. * * @param msg the detail message * @param cause the nested exception */ public NestedIOException(String msg, Throwable cause) { super(msg, cause); } /** * Return the detail message, including the message from the nested exception * if there is one. */ @Override public String getMessage() { String message = super.getMessage(); if (getCause() != null) { StringBuilder sb = new StringBuilder(); if (message != null) { sb.append(message).append("; "); } sb.append("nested exception is ").append(getCause()); return sb.toString(); } else { return message; } } } ================================================ FILE: gamioo-common/src/main/java/io/gamioo/common/exception/NoPublicFieldException.java ================================================ package io.gamioo.common.exception; /** * 在找不到指定Public属性时抛出. * * @author 小流氓[176543888@qq.com] * @since 3.3.6 */ public class NoPublicFieldException extends ServiceException { public NoPublicFieldException(String message,Object... params) { super(message, params); } public NoPublicFieldException(String message, Throwable cause) { super(message, cause); } public NoPublicFieldException(Throwable cause, String message, Object... params) { super(cause, message, params); } } ================================================ FILE: gamioo-common/src/main/java/io/gamioo/common/exception/NoPublicMethodException.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.common.exception; /** * 在找不到指定Public方法时抛出. * * @author Allen Jiang * @since 1.0.0 */ public class NoPublicMethodException extends RuntimeException { private static final long serialVersionUID = -4256184544259394170L; public NoPublicMethodException(String msg) { super(msg); } } ================================================ FILE: gamioo-common/src/main/java/io/gamioo/common/exception/ServerBootstrapException.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.common.exception; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public class ServerBootstrapException extends ServiceException{ public ServerBootstrapException(String message,Object... params) { super(message, params); } public ServerBootstrapException(String message, Throwable cause) { super(message, cause); } public ServerBootstrapException(Throwable cause, String message, Object... params) { super(cause, message, params); } } ================================================ FILE: gamioo-common/src/main/java/io/gamioo/common/exception/ServiceException.java ================================================ package io.gamioo.common.exception; import io.gamioo.common.util.StringUtils; /** * 框架异常类 * * @author Allen Jiang * @since 1.0 */ public class ServiceException extends RuntimeException { /** * */ private static final long serialVersionUID = -3287463281746412649L; //private int errorCode; public ServiceException() { super(); } public ServiceException(int code) { super(String.valueOf(code)); } public ServiceException(Throwable cause,String message,Object... params){ super(StringUtils.format(message, params),cause); } public ServiceException(String message) { super(message); } public ServiceException(String message,Object... params){ super(StringUtils.format(message, params)); } public ServiceException(String message, Throwable cause) { super(message, cause); } public ServiceException(Throwable cause) { super(cause); } } ================================================ FILE: gamioo-common/src/main/java/io/gamioo/common/http/RequestMethod.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.common.http; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public enum RequestMethod { GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE } ================================================ FILE: gamioo-common/src/main/java/io/gamioo/common/lang/Cache.java ================================================ package io.gamioo.common.lang; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public class Cache { private int type; private String ip; private int port; private int index; private String password; public Cache(){ } public Cache(int type, String ip, int port, int index, String password) { this.type = type; this.ip = ip; this.port = port; this.index = index; this.password = password; } public int getType() { return type; } public void setType(int type) { this.type = type; } public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } public int getPort() { return port; } public void setPort(int port) { this.port = port; } public int getIndex() { return index; } public void setIndex(int index) { this.index = index; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Override public String toString() { return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); } } ================================================ FILE: gamioo-common/src/main/java/io/gamioo/common/lang/Server.java ================================================ package io.gamioo.common.lang; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import java.util.Date; public class Server { private String name; private int id; private String type; private String args;// 启动参数 private String innerIp; private String externalIp; private int innerPort; private int tcpPort; private int webPort; // private int followerId; private int status; // private String pid; private String serverVersion; private String dataVersion; private Date startTime;// 启动时间 public Server() { } public String getType() { return type; } public String getArgs() { return args; } public void setArgs(String args) { this.args = args; } public void setType(String type) { this.type = type; } public Date getStartTime() { return startTime; } public void setStartTime(Date startTime) { this.startTime = startTime; } public String getServerVersion() { return serverVersion; } public void setServerVersion(String serverVersion) { if (serverVersion == null) { this.serverVersion = ""; } else { this.serverVersion = serverVersion; } } public String getDataVersion() { return dataVersion; } public void setDataVersion(String dataVersion) { this.dataVersion = dataVersion; } public int getWebPort() { return webPort; } public void setWebPort(int webPort) { this.webPort = webPort; } public int getTcpPort() { return tcpPort; } public void setTcpPort(int tcpPort) { this.tcpPort = tcpPort; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } public int getInnerPort() { return innerPort; } public void setInnerPort(int innerPort) { this.innerPort = innerPort; } public String getInnerIp() { return innerIp; } public void setInnerIp(String innerIp) { this.innerIp = innerIp; } public String getExternalIp() { return externalIp; } public void setExternalIp(String externalIp) { this.externalIp = externalIp; } public int getStatus() { return status; } public void setStatus(int status) { this.status = status; } public String toString() { return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); } } ================================================ FILE: gamioo-common/src/main/java/io/gamioo/common/lang/ServerInfo.java ================================================ package io.gamioo.common.lang; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import java.util.Date; public class ServerInfo { private int id; private String name; private int times; private Date addTime; 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 int getTimes() { return times; } public void setTimes(int times) { this.times = times; } public Date getAddTime() { return addTime; } public void setAddTime(Date addTime) { this.addTime = addTime; } @Override public String toString() { return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); } } ================================================ FILE: gamioo-common/src/main/java/io/gamioo/common/shape/AABB.java ================================================ package io.gamioo.common.shape; /** * @author Allen Jiang */ public class AABB implements Shape { private final int left; private final int top; private final int right; private final int bottom; public AABB(int left, int top, int right, int bottom) { this.left = left; this.top = top; this.right = right; this.bottom = bottom; } public int getLeft() { return left; } public int getTop() { return top; } public int getRight() { return right; } public int getBottom() { return bottom; } public int getCenterX() { return (left + right + 1) / 2; } public int getCenterY() { return (top + bottom + 1) / 2; } public int getWidth() { return right - left + 1; } public int getHeight() { return bottom - top + 1; } @Override public String toString() { return "AABB{" + "left=" + left + ", top=" + top + ", right=" + right + ", bottom=" + bottom + '}'; } @Override public boolean containsPoint(long x, long y) { return left <= x && x <= right && top <= y && y <= bottom; } @Override public AABB getAABB() { return this; } @Override public Point getRandomPoint() { return null; } } ================================================ FILE: gamioo-common/src/main/java/io/gamioo/common/shape/Point.java ================================================ package io.gamioo.common.shape; import io.gamioo.common.vector.Vector2f; public class Point implements Shape { private final int x; private final int y; private AABB aabb; public static Point valueOf(int x, int y) { return new Point(x, y); } public static Point valueOf(float x, float y) { return new Point((int) x, (int) y); } private Point(int x, int y) { this.x = x; this.y = y; } public int getX() { return x; } public int getY() { return y; } /** * 获取两点的距离 单位 厘米 * @param a 第一个点 * @param b 第二个点 * @return 距离 */ public static double calDisBetweenTwoPoint(Point a, Point b) { long xDis = a.x - b.x; long yDis = a.y - b.y; return Math.sqrt(xDis * xDis + yDis * yDis); } /** * 绕着point 旋转 radian弧度后得到的点 * @param point 旋转中心点 * @param radians 旋转角度 * @return 旋转后的点 */ public Point rotate(Point point, double radians) { double sin = Math.sin(radians); double cos = Math.cos(radians); int x = (int) ((this.x - point.getX()) * cos - (this.y - point.getY()) * sin + point.getX()); int y = (int) ((this.x - point.getX()) * sin + (this.y - point.getY()) * cos + point.getY()); return Point.valueOf(x, y); } /** * 线性插值计算线段中的点 * @param a 开始点 * @param b 结束点 * @param t 解 * @return 插值后的点 * */ public static Point lerpPoint(Point a, Point b, float t) { int ax = a.getX(); int bx = b.getX(); int ay = a.getY(); int by = b.getY(); return Point.valueOf((int) (ax + (bx - ax) * t), (int) (ay + (by - ay) * t)); } /** * 获取a点到b点 距离b点 指定距离的点 * @param a 开始点 * @param b 结束点 * @param dis 距离 * @return 指定距离的点 */ public static Point getPointWithDisToEndPoint(Point a, Point b, int dis) { int ax = a.getX(); int bx = b.getX(); int ay = a.getY(); int by = b.getY(); double abDis = Math.sqrt((ax - bx) * (ax - bx) + (ay - by) * (ay - by)); // 超出距离 则后退 // if (abDis < dis) { // throw new ServiceException("a:{} b:{} dis:{}", a, b, dis); // } return lerpPoint(a, b, (float) ((abDis - dis) / abDis)); } /** * 获取a点到b点 距离a点 指定距离的点 * @param a 开始点 * @param b 结束点 * @param dis 距离 * @return 指定距离的点 */ public static Point getPointWithDisToSrcPoint(Point a, Point b, float dis) { long ax = a.getX(); long bx = b.getX(); long ay = a.getY(); long by = b.getY(); float abDis = (float) Math.sqrt((ax - bx) * (ax - bx) + (ay - by) * (ay - by)); // if (abDis < dis) { // throw new ServiceException("a:{} b:{} dis:{}", a, b, dis); // } return lerpPoint(a, b, dis / abDis); } /** * 通过起点、向量、距离获取终点 * @param vector 向量 * @param dis 距离 * @return 终点 */ public Point getPointWithVectorAndDis(Vector2f vector, float dis) { Vector2f newVector = vector.resizeLength(dis); return Point.valueOf(x + (int) newVector.getX(), y + (int) newVector.getY()); } /** * @param x 点x * @param y 点y * */ @Override public boolean containsPoint(long x, long y) { return this.x == x && this.y == y; } @Override public AABB getAABB() { if (aabb == null) { aabb = new AABB(x, y, x, y); } return aabb; } @Override public Point getRandomPoint() { return Point.valueOf(x, y); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof Point)) { return false; } Point point = (Point) obj; return this.x == point.x && this.y == point.y; } /** * 大体上相同 允许精度误差 * @param point 要比较的点 * @param accuracyErrorSize 精度误差 * @return 相同返回true 不同返回false */ public boolean roughlyEquals(Point point, final int accuracyErrorSize) { if (this == point) { return true; } return Math.abs(point.getX() - getX()) < accuracyErrorSize && Math.abs(point.getY() - getY()) < accuracyErrorSize; } @Override public String toString() { return "Point{" + "x=" + x + ", y=" + y + '}'; } } ================================================ FILE: gamioo-common/src/main/java/io/gamioo/common/shape/Sector.java ================================================ package io.gamioo.common.shape; import io.gamioo.common.util.MathUtils; import io.gamioo.common.vector.Vector2f; import org.apache.commons.lang3.RandomUtils; import java.util.ArrayList; import java.util.List; /** * 扇形 * * @author Allen Jiang */ public class Sector implements Shape { private Point center; private Point pointA; private Point pointB; private final int r; private final int startAngle; private final int endAngle; private AABB aabb = null; /** * 扇形构造函数 * * @param one 原点 * @param other 目标点 * @param radius 半径 * @param angle 扇形全角 * @return know 形实例 */ public static Sector valueOf(Point one, Point other, int radius, int angle) { return new Sector(one, other, radius, angle); } private Sector(Point one, Point other, int radius, int angle) { this.center = Point.valueOf(one.getX(), one.getY()); this.r = radius; Vector2f base = Vector2f.getVectorFromPointToPoint(one, other); Vector2f left = base.rotate(angle / 2).resizeLength(radius); this.pointA = Point.valueOf(left.getX() + this.center.getX(), left.getY() + this.center.getY()); Vector2f right = left.rotate(-angle); this.pointB = Point.valueOf(right.getX() + this.center.getX(), right.getY() + this.center.getY()); this.startAngle = (int) left.getAngle(); this.endAngle = this.startAngle + angle; } @Override public boolean containsPoint(long x, long y) { return pointWithinRadius(x, y) && angleContainPoint(x, y); } /** * 忽略角度判断某点是否在扇形半径内 * @param x 坐标x * @param y 坐标y * @return true 坐标在bigint形范围内 ; false 坐标在bigint形范围外 */ public boolean pointWithinRadius(long x, long y) { long distance = (x - this.center.getX()) * (x - this.center.getX()) + (y - this.center.getY()) * (y - this.center.getY()); return distance <= (long) r * r; } public boolean containsPoint(Point point) { return this.containsPoint(point.getX(), point.getY()); } @Override public AABB getAABB() { if (aabb == null) { List listX = new ArrayList<>(); List listY = new ArrayList<>(); Point center = this.center; listX.add(center.getX()); listY.add(center.getY()); Point pointA = this.pointA; listX.add(pointA.getX()); listY.add(pointA.getY()); Point pointB = this.pointB; listX.add(pointB.getX()); listY.add(pointB.getY()); Point pointC = Point.valueOf(this.center.getX() - r, this.center.getY()); Point pointD = Point.valueOf(this.center.getX() + r, this.center.getY()); Point pointE = Point.valueOf(this.center.getX(), this.center.getY() + r); Point pointF = Point.valueOf(this.center.getX(), this.center.getY() - r); if (this.containsPoint(pointC)) { listX.add(pointC.getX()); listY.add(pointC.getY()); } if (this.containsPoint(pointD)) { listX.add(pointD.getX()); listY.add(pointD.getY()); } if (this.containsPoint(pointE)) { listX.add(pointE.getX()); listY.add(pointE.getY()); } if (this.containsPoint(pointF)) { listX.add(pointF.getX()); listY.add(pointF.getY()); } int left = MathUtils.min(listX); int right = MathUtils.max(listX); int top = MathUtils.min(listY); int bottom = MathUtils.max(listY); aabb = new AABB(left, top, right, bottom); } return aabb; } @Override public Point getRandomPoint() { int length = RandomUtils.nextInt(1, r); int angle; if (this.startAngle < 0) { angle = (int) RandomUtils.nextDouble(this.startAngle + 1 + 360, this.endAngle + 360); } else { angle = (int) RandomUtils.nextDouble(this.startAngle + 1, this.endAngle); } Vector2f result = Vector2f.valueOf(length, angle); return Point.valueOf(result.getX() + this.center.getX(), result.getY() + this.center.getY()); } public Point getCenter() { return center; } public void setCenter(Point center) { this.center = center; } public Point getPointA() { return pointA; } public void setPointA(Point pointA) { this.pointA = pointA; } public Point getPointB() { return pointB; } public void setPointB(Point pointB) { this.pointB = pointB; } public int getR() { return r; } public double getStartAngle() { return startAngle; } public double getEndAngle() { return endAngle; } /** * 判断点是否在扇形夹角中 * * @param x x坐标 * @param y y坐标 * @return true表示在扇形夹角中 */ public boolean angleContainPoint(long x, long y) { double angle = Math.atan2(y - getCenter().getY(), x - getCenter().getX()); angle = Math.toDegrees(angle); return angle >= startAngle && angle <= endAngle; } } ================================================ FILE: gamioo-common/src/main/java/io/gamioo/common/shape/Shape.java ================================================ package io.gamioo.common.shape; public interface Shape { /** * 是否包含指定的点 * * @param x 点x * @param y 点y * * @return true:包含 */ boolean containsPoint(long x, long y); /** * 获取aabb包围盒 * * @return 包围盒 */ AABB getAABB(); /** * 获取图形内随机一个点 * * @return 点 */ Point getRandomPoint(); } ================================================ FILE: gamioo-common/src/main/java/io/gamioo/common/util/AnnotationUtils.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.common.util; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; /** * 注解工具类. * * @author Allen Jiang * @since 1.0.0 */ public class AnnotationUtils { private AnnotationUtils() { } /** * 获取指定类型的注解或注解上有指定的注解. * * @param element 注解元素 * @param annotationType 注解类型 * @param 注解类型 * @return 返回标识有指定注解的注解 */ public static Annotation getAnnotation(AnnotatedElement element, Class annotationType) { A annotation = element.getAnnotation(annotationType); if (annotation == null) { for (Annotation metaAnn : element.getAnnotations()) { annotation = metaAnn.annotationType().getAnnotation(annotationType); if (annotation != null) { return metaAnn; } } } return annotation; } } ================================================ FILE: gamioo-common/src/main/java/io/gamioo/common/util/ArrayUtils.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.common.util; import java.util.Arrays; /** * 数组相关操作工具类. * * @author Allen Jiang * @since 1.0.0 */ public class ArrayUtils { /** * 一个空的字符串数组. */ public static final String[] EMPTY_STRING_ARRAY = {}; /** *

* Adds all the elements of the given arrays into a new array. *

*

* The new array contains all of the element of {@code array1} followed by * all of the elements {@code array2}. When an array is returned, it is * always a new array. *

* *
     * ArrayUtils.addAll([], [])         = []
     * 
* * @param array1 the first array whose elements are added to the new array. * @param array2 the second array whose elements are added to the new array. * @return The new long[] array. */ public static long[] addAll(final long[] array1, final long... array2) { final long[] joinedArray = new long[array1.length + array2.length]; System.arraycopy(array1, 0, joinedArray, 0, array1.length); System.arraycopy(array2, 0, joinedArray, array1.length, array2.length); return joinedArray; } /** * 判定Object数组是否为空(null或长度为0). * * @param array Object数组 * @return 如果为空(null或长度为0)则返回true, 否则返回false. */ public static boolean isEmpty(final Object[] array) { return array == null || array.length == 0; } /** * 判定Object数组是否不为空(null或长度为0). * * @param array Object数组 * @return 如果不为空(null或长度为0)则返回true, 否则返回false. */ public static boolean isNotEmpty(final Object[] array) { return !isEmpty(array); } /** * 判定byte数组是否为空(null或长度为0). * * @param array byte数组 * @return 如果为空(null或长度为0)则返回true, 否则返回false. */ public static boolean isEmpty(final byte[] array) { return array == null || array.length == 0; } /** * 判定byte数组是否不为空(null或长度为0). * * @param array byte数组 * @return 如果不为空(null或长度为0)则返回true, 否则返回false. */ public static boolean isNotEmpty(final byte[] array) { return !isEmpty(array); } /** * 判定boolean数组是否为空(null或长度为0). * * @param array boolean数组 * @return 如果为空(null或长度为0)则返回true, 否则返回false. */ public static boolean isEmpty(final boolean[] array) { return array == null || array.length == 0; } /** * 判定boolean数组是否不为空(null或长度为0). * * @param array boolean数组 * @return 如果不为空(null或长度为0)则返回true, 否则返回false. */ public static boolean isNotEmpty(final boolean[] array) { return !isEmpty(array); } /** * 判定int数组是否为空(null或长度为0). * * @param array int数组 * @return 如果为空(null或长度为0)则返回true, 否则返回false. */ public static boolean isEmpty(final int[] array) { return array == null || array.length == 0; } /** * 判定int数组是否不为空(null或长度为0). * * @param array int数组 * @return 如果不为空(null或长度为0)则返回true, 否则返回false. */ public static boolean isNotEmpty(final int[] array) { return !isEmpty(array); } /** * 判定long数组是否为空(null或长度为0). * * @param array long数组 * @return 如果为空(null或长度为0)则返回true, 否则返回false. */ public static boolean isEmpty(final long[] array) { return array == null || array.length == 0; } /** * 判定long数组是否不为空(null或长度为0). * * @param array long数组 * @return 如果不为空(null或长度为0)则返回true, 否则返回false. */ public static boolean isNotEmpty(final long[] array) { return !isEmpty(array); } /** * 判定float数组是否为空(null或长度为0). * * @param array float数组 * @return 如果为空(null或长度为0)则返回true, 否则返回false. */ public static boolean isEmpty(final float[] array) { return array == null || array.length == 0; } /** * 判定float数组是否不为空(null或长度为0). * * @param array float数组 * @return 如果不为空(null或长度为0)则返回true, 否则返回false. */ public static boolean isNotEmpty(final float[] array) { return !isEmpty(array); } /** * 判定double数组是否为空(null或长度为0). * * @param array double数组 * @return 如果为空(null或长度为0)则返回true, 否则返回false. */ public static boolean isEmpty(final double[] array) { return array == null || array.length == 0; } /** * 判定double数组是否不为空(null或长度为0). * * @param array double数组 * @return 如果不为空(null或长度为0)则返回true, 否则返回false. */ public static boolean isNotEmpty(final double[] array) { return !isEmpty(array); } /** * String数组转化为int数组 * * @param array String数组 * @return int数组 */ public static int[] toIntArray(String[] array) { return Arrays.stream(array).mapToInt(s -> Integer.parseInt(s)).toArray(); } /** * Integer数组转化为int数组 * * @param array Integer数组 * @return int数组 */ public static int[] toIntArray(Integer[] array) { return Arrays.stream(array).mapToInt(Integer::intValue).toArray(); } /** * String数组转化为long数组 * * @param array String数组 * @return long数组 */ public static long[] toLongArray(String[] array) { return Arrays.stream(array).mapToLong(s -> Long.parseLong(s)).toArray(); } /** * Long数组转化为long数组 * * @param array Long数组 * @return long数组 */ public static long[] toLongArray(Long[] array) { return Arrays.stream(array).mapToLong(Long::longValue).toArray(); } /** * 字符串组数转化为字节数组. *

* 默认使用10进制解析 * * @param array 字符串组数 * @return 转化后的字节数组 */ public static byte[] toByteArray(String[] array) { return toByteArray(array, 10); } /** * 字符串组数转化为字节数组. * * @param array 字符串组数 * @param radix 角色字符串数组{@code array}时所用的进制 * @return 转化后的字节数组 */ public static byte[] toByteArray(String[] array, int radix) { final byte[] data = new byte[array.length]; for (int i = 0; i < array.length; i++) { data[i] = (byte) Integer.parseInt(array[i], radix); } return data; } /** * 求数组长度. *

* 如果数组为null,则返回0 * * @param array 字符串数组 * @return 返回字符串数组长度 */ public static int length(String[] array) { return array == null ? 0 : array.length; } } ================================================ FILE: gamioo-common/src/main/java/io/gamioo/common/util/Assert.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.common.util; import org.apache.commons.lang3.ObjectUtils; import java.util.Collection; import java.util.Map; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public class Assert { /** * Assert a boolean expression, throwing {@code IllegalArgumentException} * if the test result is {@code false}. *

Assert.isTrue(i > 0, "The value must be greater than zero");
* * @param expression a boolean expression * @param message the exception message to use if the assertion fails * @throws IllegalArgumentException if expression is {@code false} */ public static void isTrue(boolean expression, String message) { if (!expression) { throw new IllegalArgumentException(message); } } /** * Assert a boolean expression, throwing {@code IllegalArgumentException} * if the test result is {@code false}. *
Assert.isTrue(i > 0);
* * @param expression a boolean expression * @throws IllegalArgumentException if expression is {@code false} */ public static void isTrue(boolean expression) { isTrue(expression, "[Assertion failed] - this expression must be true"); } /** * Assert that an object is {@code null} . *
Assert.isNull(value, "The value must be null");
* * @param object the object to check * @param message the exception message to use if the assertion fails * @throws IllegalArgumentException if the object is not {@code null} */ public static void isNull(Object object, String message) { if (object != null) { throw new IllegalArgumentException(message); } } /** * Assert that an object is {@code null} . *
Assert.isNull(value);
* * @param object the object to check * @throws IllegalArgumentException if the object is not {@code null} */ public static void isNull(Object object) { isNull(object, "[Assertion failed] - the object argument must be null"); } /** * Assert that an object is not {@code null} . *
Assert.notNull(clazz, "The class must not be null");
* * @param object the object to check * @param message the exception message to use if the assertion fails * @throws IllegalArgumentException if the object is {@code null} */ public static void notNull(Object object, String message) { if (object == null) { throw new IllegalArgumentException(message); } } /** * Assert that an object is not {@code null} . *
Assert.notNull(clazz);
* * @param object the object to check * @throws IllegalArgumentException if the object is {@code null} */ public static void notNull(Object object) { notNull(object, "[Assertion failed] - this argument is required; it must not be null"); } /** * Assert that the given String is not empty; that is, * it must not be {@code null} and not the empty String. *
Assert.hasLength(name, "Name must not be empty");
* * @param text the String to check * @param message the exception message to use if the assertion fails * @throws IllegalArgumentException if the text is empty * @see StringUtils#hasLength */ public static void hasLength(String text, String message) { if (!StringUtils.hasLength(text)) { throw new IllegalArgumentException(message); } } /** * Assert that the given String is not empty; that is, * it must not be {@code null} and not the empty String. *
Assert.hasLength(name);
* * @param text the String to check * @throws IllegalArgumentException if the text is empty * @see StringUtils#hasLength */ public static void hasLength(String text) { hasLength(text, "[Assertion failed] - this String argument must have length; it must not be null or empty"); } /** * Assert that the given String has valid text content; that is, it must not * be {@code null} and must contain at least one non-whitespace character. *
Assert.hasText(name, "'name' must not be empty");
* * @param text the String to check * @param message the exception message to use if the assertion fails * @throws IllegalArgumentException if the text does not contain valid text content * @see StringUtils#hasText */ public static void hasText(String text, String message) { if (!StringUtils.hasText(text)) { throw new IllegalArgumentException(message); } } /** * Assert that the given String has valid text content; that is, it must not * be {@code null} and must contain at least one non-whitespace character. *
Assert.hasText(name, "'name' must not be empty");
* * @param text the String to check * @throws IllegalArgumentException if the text does not contain valid text content * @see StringUtils#hasText */ public static void hasText(String text) { hasText(text, "[Assertion failed] - this String argument must have text; it must not be null, empty, or blank"); } /** * Assert that the given text does not contain the given substring. *
Assert.doesNotContain(name, "rod", "Name must not contain 'rod'");
* * @param textToSearch the text to search * @param substring the substring to find within the text * @param message the exception message to use if the assertion fails * @throws IllegalArgumentException if the text contains the substring */ public static void doesNotContain(String textToSearch, String substring, String message) { if (StringUtils.hasLength(textToSearch) && StringUtils.hasLength(substring) && textToSearch.contains(substring)) { throw new IllegalArgumentException(message); } } /** * Assert that the given text does not contain the given substring. *
Assert.doesNotContain(name, "rod");
* * @param textToSearch the text to search * @param substring the substring to find within the text * @throws IllegalArgumentException if the text contains the substring */ public static void doesNotContain(String textToSearch, String substring) { doesNotContain(textToSearch, substring, "[Assertion failed] - this String argument must not contain the substring [" + substring + "]"); } /** * Assert that an array has elements; that is, it must not be * {@code null} and must have at least one element. *
Assert.notEmpty(array, "The array must have elements");
* * @param array the array to check * @param message the exception message to use if the assertion fails * @throws IllegalArgumentException if the object array is {@code null} or has no elements */ public static void notEmpty(Object[] array, String message) { if (ObjectUtils.isEmpty(array)) { throw new IllegalArgumentException(message); } } /** * Assert that an array has elements; that is, it must not be * {@code null} and must have at least one element. *
Assert.notEmpty(array);
* * @param array the array to check * @throws IllegalArgumentException if the object array is {@code null} or has no elements */ public static void notEmpty(Object[] array) { notEmpty(array, "[Assertion failed] - this array must not be empty: it must contain at least 1 element"); } /** * Assert that an array has no null elements. * Note: Does not complain if the array is empty! *
Assert.noNullElements(array, "The array must have non-null elements");
* * @param array the array to check * @param message the exception message to use if the assertion fails * @throws IllegalArgumentException if the object array contains a {@code null} element */ public static void noNullElements(Object[] array, String message) { if (array != null) { for (Object element : array) { if (element == null) { throw new IllegalArgumentException(message); } } } } /** * Assert that an array has no null elements. * Note: Does not complain if the array is empty! *
Assert.noNullElements(array);
* * @param array the array to check * @throws IllegalArgumentException if the object array contains a {@code null} element */ public static void noNullElements(Object[] array) { noNullElements(array, "[Assertion failed] - this array must not contain any null elements"); } /** * Assert that a collection has elements; that is, it must not be * {@code null} and must have at least one element. *
Assert.notEmpty(collection, "Collection must have elements");
* * @param collection the collection to check * @param message the exception message to use if the assertion fails * @throws IllegalArgumentException if the collection is {@code null} or has no elements */ public static void notEmpty(Collection collection, String message) { if (collection == null || collection.isEmpty()) { throw new IllegalArgumentException(message); } } /** * Assert that a collection has elements; that is, it must not be * {@code null} and must have at least one element. *
Assert.notEmpty(collection, "Collection must have elements");
* * @param collection the collection to check * @throws IllegalArgumentException if the collection is {@code null} or has no elements */ public static void notEmpty(Collection collection) { notEmpty(collection, "[Assertion failed] - this collection must not be empty: it must contain at least 1 element"); } /** * Assert that a Map has entries; that is, it must not be {@code null} * and must have at least one entry. *
Assert.notEmpty(map, "Map must have entries");
* * @param map the map to check * @param message the exception message to use if the assertion fails * @throws IllegalArgumentException if the map is {@code null} or has no entries */ public static void notEmpty(Map map, String message) { if (map == null || map.isEmpty()) { throw new IllegalArgumentException(message); } } /** * Assert that a Map has entries; that is, it must not be {@code null} * and must have at least one entry. *
Assert.notEmpty(map);
* * @param map the map to check * @throws IllegalArgumentException if the map is {@code null} or has no entries */ public static void notEmpty(Map map) { notEmpty(map, "[Assertion failed] - this map must not be empty; it must contain at least one entry"); } /** * Assert that the provided object is an instance of the provided class. *
Assert.instanceOf(Foo.class, foo);
* * @param clazz the required class * @param obj the object to check * @throws IllegalArgumentException if the object is not an instance of clazz * @see Class#isInstance */ public static void isInstanceOf(Class clazz, Object obj) { isInstanceOf(clazz, obj, ""); } /** * Assert that the provided object is an instance of the provided class. *
Assert.instanceOf(Foo.class, foo);
* * @param type the type to check against * @param obj the object to check * @param message a message which will be prepended to the message produced by * the function itself, and which may be used to provide context. It should * normally end in ":" or "." so that the generated message looks OK when * appended to it. * @throws IllegalArgumentException if the object is not an instance of clazz * @see Class#isInstance */ public static void isInstanceOf(Class type, Object obj, String message) { notNull(type, "Type to check against must not be null"); if (!type.isInstance(obj)) { throw new IllegalArgumentException( (StringUtils.hasLength(message) ? message + " " : "") + "Object of class [" + (obj != null ? obj.getClass().getName() : "null") + "] must be an instance of " + type); } } /** * Assert that {@code superType.isAssignableFrom(subType)} is {@code true}. *
Assert.isAssignable(Number.class, myClass);
* * @param superType the super type to check * @param subType the sub type to check * @throws IllegalArgumentException if the classes are not assignable */ public static void isAssignable(Class superType, Class subType) { isAssignable(superType, subType, ""); } /** * Assert that {@code superType.isAssignableFrom(subType)} is {@code true}. *
Assert.isAssignable(Number.class, myClass);
* * @param superType the super type to check against * @param subType the sub type to check * @param message a message which will be prepended to the message produced by * the function itself, and which may be used to provide context. It should * normally end in ":" or "." so that the generated message looks OK when * appended to it. * @throws IllegalArgumentException if the classes are not assignable */ public static void isAssignable(Class superType, Class subType, String message) { notNull(superType, "Type to check against must not be null"); if (subType == null || !superType.isAssignableFrom(subType)) { throw new IllegalArgumentException((StringUtils.hasLength(message) ? message + " " : "") + subType + " is not assignable to " + superType); } } /** * Assert a boolean expression, throwing {@code IllegalStateException} * if the test result is {@code false}. Call isTrue if you wish to * throw IllegalArgumentException on an assertion failure. *
Assert.state(id == null, "The id property must not already be initialized");
* * @param expression a boolean expression * @param message the exception message to use if the assertion fails * @throws IllegalStateException if expression is {@code false} */ public static void state(boolean expression, String message) { if (!expression) { throw new IllegalStateException(message); } } /** * Assert a boolean expression, throwing {@link IllegalStateException} * if the test result is {@code false}. *

Call {@link #isTrue(boolean)} if you wish to * throw {@link IllegalArgumentException} on an assertion failure. *

Assert.state(id == null);
* * @param expression a boolean expression * @throws IllegalStateException if the supplied expression is {@code false} */ public static void state(boolean expression) { state(expression, "[Assertion failed] - this state invariant must be true"); } } ================================================ FILE: gamioo-common/src/main/java/io/gamioo/common/util/ByteArrayUtils.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.common.util; /** * 字节数组操作工具类. * * @author Allen Jiang * @since 1.0.0 */ public class ByteArrayUtils { /** * 一个空的字节数组. */ public static final byte[] EMPTY_BYTE_ARRAY = {}; /** * 一个short类型的数字转化为2位byte数组 * * @param a short类型的数字 * @return byte数组 */ public static byte[] toByteArray(short a) { return new byte[]{(byte) ((a >> 8) & 0xFF), (byte) (a & 0xFF)}; } /** * 一个int类型的数字转化为4位byte数组 * * @param num int类型的数字 * @return byte数组 */ public static byte[] toByteArray(int num) { return new byte[]{(byte) ((num >> 24) & 0xFF), (byte) ((num >> 16) & 0xFF), (byte) ((num >> 8) & 0xFF), (byte) (num & 0xFF)}; } /** * 4位byte数组转化为一个int类型的数字 * * @param bytes byte数组 * @return int类型的数字 */ public static int toInt(byte[] bytes) { return bytes[3] & 0xFF | (bytes[2] & 0xFF) << 8 | (bytes[1] & 0xFF) << 16 | (bytes[0] & 0xFF) << 24; } } ================================================ FILE: gamioo-common/src/main/java/io/gamioo/common/util/CharsetUtils.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.common.util; import java.nio.charset.Charset; /** * 字符集工具类 * * @author Allen Jiang * @since 1.0.0 */ public class CharsetUtils { /** * ISO-8859-1 */ public static final String ISO_8859_1 = "ISO-8859-1"; /** * UTF-8 */ public static final String UTF_8 = "UTF-8"; /** * GBK */ public static final String GBK = "GBK"; /** * ISO-8859-1 */ public static final Charset CHARSET_ISO_8859_1 = Charset.forName(ISO_8859_1); /** * UTF-8 */ public static final Charset CHARSET_UTF_8 = Charset.forName(UTF_8); /** * GBK */ public static final Charset CHARSET_GBK = Charset.forName(GBK); } ================================================ FILE: gamioo-common/src/main/java/io/gamioo/common/util/ClassUtils.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.common.util; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public class ClassUtils { /** * Return the default ClassLoader to use: typically the thread context * ClassLoader, if available; the ClassLoader that loaded the ClassUtils * class will be used as fallback. *

Call this method if you intend to use the thread context ClassLoader * in a scenario where you clearly prefer a non-null ClassLoader reference: * for example, for class path resource loading (but not necessarily for * {@code Class.forName}, which accepts a {@code null} ClassLoader * reference as well). * @return the default ClassLoader (only {@code null} if even the system * ClassLoader isn't accessible) * @see Thread#getContextClassLoader() * @see ClassLoader#getSystemClassLoader() */ public static ClassLoader getDefaultClassLoader() { ClassLoader ret = null; try { ret = Thread.currentThread().getContextClassLoader(); } catch (Throwable ex) { // Cannot access thread context ClassLoader - falling back... } if (ret == null) { // No thread context class loader -> use class loader of this class. ret = ClassUtils.class.getClassLoader(); if (ret == null) { // getClassLoader() returning null indicates the bootstrap ClassLoader try { ret = ClassLoader.getSystemClassLoader(); } catch (Throwable ex) { // Cannot access system ClassLoader - oh well, maybe the caller can live with null... } } } return ret; } /** * 使用当前线程的ClassLoader加载给定的类 * * @param className 类的全称 * @return 给定的类 */ public static Class loadClass(String className) { // ClassLoader#loadClass(String):将.class文件加载到JVM中,不会执行static块,只有在创建实例时才会去执行static块 try { return Thread.currentThread().getContextClassLoader().loadClass(className); } catch (ClassNotFoundException e) { } // Class#forName(String):将.class文件加载到JVM中,还会对类进行解释,并执行类中的static块 try { return Class.forName(className); } catch (ClassNotFoundException e) { } throw new RuntimeException("无法加载指定类名的Class=" + className); } /** * 创建一个指定类的对象,调用默认的构造函数. * * @param Class * @param klass 类 * @return 指定类的对象 */ public static T newInstance(final Class klass) { try { return klass.newInstance(); } catch (InstantiationException | IllegalAccessException e) { throw new RuntimeException("无法创建实例. Class=" + klass.getName(), e); } } /** * 根据ClassName和构造方法的参数列表来创建一个对象 * * @param Class * @param className 指定类全名(包含包名称的那种) * @param parameters 参数列表 * @return 指定ClassName的对象 */ @SuppressWarnings("unchecked") public static T newInstance(String className, Object... parameters) { Class klass = (Class) loadClass(className); try { Class[] parameterTypes = new Class[parameters.length]; for (int i = 0, len = parameters.length; i < len; i++) { parameterTypes[i] = parameters[i].getClass(); } return (T) klass.getConstructor(parameterTypes).newInstance(parameters); } catch (Exception e) { throw new RuntimeException("无法创建实例. Class=" + klass.getName(), e); } } // /** // * 尝试运行一个带有Main方法的类. // * // * @param mainClass 带有Main方法类的名称 // * @param args 启动参数数组 // */ // public static void invokeMain(String mainClass, String[] args) { // final Class klass = ClassUtils.loadClass(mainClass); // Method mainMethod = MethodUtils.getMethod(klass, "main", String[].class); // MethodUtils.invoke(null, mainMethod, new Object[]{args}); // } } ================================================ FILE: gamioo-common/src/main/java/io/gamioo/common/util/FieldUtils.java ================================================ package io.gamioo.common.util; import io.gamioo.common.exception.NoPublicFieldException; import io.gamioo.common.exception.ServerBootstrapException; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.*; /** * 属性工具类. * * @author Allen Jiang * @since 1.0.0 */ public class FieldUtils { private static final int PREFIX_IS_METHOD_INDEX = 2; private static final int PREFIX_GET_METHOD_INDEX = 3; private static final int PREFIX_SET_METHOD_INDEX = 3; /** * 强制给一个属性{@link Field}写入值. * * @param target 目标对象 * @param field 要写入的属性 * @param value 要写入的值 */ public static void writeField(final Object target, final Field field, final Object value) { if (!field.isAccessible()) { field.setAccessible(true); } try { field.set(target, value); } catch (IllegalArgumentException | IllegalAccessException e) { throw new ServerBootstrapException(target.getClass() + " 的 " + field.getName() + " 属性无法注入.", e); } } /** * 强制给一个指定名称的属性写入值. *

* 这个基本是留言给脚本调用的,方便修改一些配置错误而生的方法(自动找父类的属性) * * @param target 目标对象 * @param fieldName 要写入的属性名称 * @param value 要写入的值 */ public static void writeField(final Object target, final String fieldName, final Object value) { Field field = FieldUtils.getField(target.getClass(), fieldName); if (field == null) { throw new NoPublicFieldException("Class={},field={} not found",target.getClass().getName(),fieldName); } FieldUtils.writeField(target, field, value); } /** * 强制读取一个属性{@link Field}的值. * * @param target 目标类对象,如果是静态方法,目标为null * @param field 对象的属性 * @return 返回对象属性的值 */ public static Object readField(final Object target, final Field field) { if (!field.isAccessible()) { field.setAccessible(true); } try { return field.get(target); } catch (IllegalArgumentException | IllegalAccessException e) { throw new ServerBootstrapException(e,"{} 的 {} 属性无法读取.",target.getClass(), field.getName()); } } /** * 获取指定名称的属性. * * @param klass 指定类 * @param fieldName 指定名称 * @return 指定名称的属性 */ public static Field getField(final Class klass, String fieldName) { for (Class target = klass; target != Object.class; target = target.getSuperclass()) { for (Field field : target.getDeclaredFields()) { if (field.getName().equals(fieldName)) { return field; } } } return null; } /** * 获取指定类的所有属性,包含父类的属性. *

* 包含私有属性,包含静态属性等等.... * * @param klass 指定类 * @return 指定类的属性集合. */ public static List getFieldList(final Class klass) { List result = new ArrayList<>(); for (Class target = klass; target != Object.class; target = target.getSuperclass()) { result.addAll(Arrays.asList(target.getDeclaredFields())); } return result; } /** * 生成Get方法名. *

* * @param field 属性 * @return Get方法名 */ public static String genGetMethodName(Field field) { int len = field.getName().length(); StringBuilder sb; if (field.getType() == boolean.class) { sb = new StringBuilder(len + 2); sb.append("is").append(field.getName()); if (Character.isLowerCase(sb.charAt(PREFIX_IS_METHOD_INDEX))) { sb.setCharAt(PREFIX_IS_METHOD_INDEX, Character.toUpperCase(sb.charAt(PREFIX_IS_METHOD_INDEX))); } } else { sb = new StringBuilder(len + 3); sb.append("get").append(field.getName()); if (Character.isLowerCase(sb.charAt(PREFIX_GET_METHOD_INDEX))) { sb.setCharAt(PREFIX_GET_METHOD_INDEX, Character.toUpperCase(sb.charAt(PREFIX_GET_METHOD_INDEX))); } } return sb.toString(); } /** * 生成Set方法名. * * @param field 属性 * @return Set方法名 */ public static String genSetMethodName(Field field) { int len = field.getName().length(); StringBuilder sb = new StringBuilder(len + 3); sb.append("set").append(field.getName()); if (Character.isLowerCase(sb.charAt(PREFIX_SET_METHOD_INDEX))) { sb.setCharAt(PREFIX_SET_METHOD_INDEX, Character.toUpperCase(sb.charAt(PREFIX_SET_METHOD_INDEX))); } return sb.toString(); } /** * 利用反射,扫描出此类所有属性(包含父类中子类没有重写的属性) * * @param klass 指定类. * @param annotations 标识属性的注解 * @return 返回此类所有属性. */ public static Field[] scanAllField(final Class klass, List> annotations) { // 为了返回是有序的添加过程,这里使用LinkedHashMap Map fieldMap = new LinkedHashMap<>(); scanField(klass, fieldMap, annotations); return fieldMap.values().toArray(new Field[0]); } /** * 递归的方式拉取属性,这样父类的属性就在上面了... * * @param klass 类 * @param fieldMap 所有属性集合 * @param annotations 标识属性的注解 */ private static void scanField(final Class klass, Map fieldMap, List> annotations) { Class superClass = klass.getSuperclass(); if (!Object.class.equals(superClass)) { scanField(superClass, fieldMap, annotations); } // 属性判定 for (Field f : klass.getDeclaredFields()) { // Static和Final的不要 if (Modifier.isStatic(f.getModifiers()) || Modifier.isFinal(f.getModifiers())) { continue; } // 子类已重写或内部类中的不要 if (fieldMap.containsKey(f.getName()) || f.getName().startsWith("this$")) { continue; } // 没有指定的注解不要 for (Annotation a : f.getAnnotations()) { if (annotations.contains(a.annotationType())) { fieldMap.put(f.getName(), f); break; } } } } /** * 获取Map类型的属性Key的Class对象. * * @param field Map类型的属性 * @return Key的Class对象 */ public static Class getMapFieldKeyClass(Field field) { Type genericType = field.getGenericType(); if (genericType instanceof ParameterizedType) { ParameterizedType pt = (ParameterizedType) genericType; // Key是第0位 return (Class) pt.getActualTypeArguments()[0]; } return Object.class; } } ================================================ FILE: gamioo-common/src/main/java/io/gamioo/common/util/FileUtils.java ================================================ package io.gamioo.common.util; import org.apache.commons.io.Charsets; import org.apache.commons.io.IOUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.io.*; import java.net.URL; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.text.DecimalFormat; import java.util.List; import java.util.Optional; /** * 文件操作工具类. * * @author Allen Jiang */ public class FileUtils { private static final Logger logger = LogManager.getLogger(FileUtils.class); private static final String URL_PROTOCOL_JAR = "jar"; /** * 可读大小的单位 */ private static final String[] UNITS = new String[]{"B", "KB", "MB", "GB", "TB", "EB"}; /** * 加载类路径下指定名称文件中的文本. * * @param fileName 文件名称 * @return 返回文件中的文本 */ public static Optional getFileText(String fileName) { try (InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName)) { return Optional.ofNullable(StringUtils.readString(is)); } catch (Exception e) { logger.error(e.getMessage(), e); } // 文件不存在等其他情况返回null return Optional.empty(); } /** * 读取指定名称文件中的文本. * * @param fileName 文件名称 * @return 返回文件中的文本 * @throws IOException If an I/O error occurs * @throws FileNotFoundException 文件未找到会抛出此异常 */ public static String readFileText(String fileName) throws FileNotFoundException, IOException { try (FileReader reader = new FileReader(fileName)) { return StringUtils.readString(reader); } } /** * 读取指定名称文件中的文本. * TODO(fix): 当fileName不存在的情况下,会导致空指针异常。 * * @param fileName 文件名称 * @return 返回文件中的文本 */ public static File getFile(String fileName) { // 通过url获取File的绝对路径 URL url = Thread.currentThread().getContextClassLoader().getResource(fileName); if (url != null) { return new File(url.getFile()); } else { return null; } } /** * 读取指定名称文件中的文本. * 获取jar包内的资源 * * @param fileName 文件名称 * @return 返回文件中的文本 */ public static File getFileFromJar(String fileName) { // 通过url获取File的绝对路径 URL url = FileUtils.class.getResource(fileName); if (url != null) { return new File(url.getFile()); } else { return null; } } /** * 读取指定名称文件中的文本. * * @param fileName 文件名称 * @return 返回文件中的文本 */ public static InputStream getInputStream(String fileName) { return Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName); } public static InputStream getInputStreamFromAll(String fileName) throws IOException { InputStream ret; URL url = ClassUtils.getDefaultClassLoader().getResource(fileName); File file = null; if (url != null && StringUtils.equals(url.getProtocol(), URL_PROTOCOL_JAR)) { ret = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName); } else { file = new File(url.getFile()); ret = openInputStream(file); } return ret; } /** * 写入指定文本到文件中. *

* 文件不存在,则会自动创建,默认是覆盖原文件 * * @param fileName 文件名称 * @param content 要写入的内容 * @throws IOException If an I/O error occurs */ public static void writeFileText(String fileName, String content) throws IOException { writeFileText(fileName, false, content); } /** * 写入指定文本到文件中. *

* 文件不存在,则会自动创建 * * @param fileName 文件名称 * @param append 是否追加写入 * @param content 要写入的内容 * @throws IOException If an I/O error occurs */ public static void writeFileText(String fileName, boolean append, String content) throws IOException { try (FileOutputStream fos = new FileOutputStream(fileName, append); OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8)) { osw.write(content); osw.flush(); } } /** * 可读的文件大小 * * @param file 文件 * @return 大小 */ public static String readableFileSize(File file) { return readableFileSize(file.length()); } /** * 可读的文件大小
* * @param size Long类型大小 * @return 大小 */ public static String readableFileSize(long size) { if (size <= 0) { return "0"; } int digitGroups = (int) (Math.log10(size) / Math.log10(1024)); return new DecimalFormat("#,##0.##").format(size / Math.pow(1024, digitGroups)) + " " + UNITS[digitGroups]; } /** * 创建指定文件,目录不存在则自动创建 *

* 如果目标文件不存在并且创建成功,则为true;如果目标文件存在,则为false * * @param file 文件对象 * @return 如果目标文件不存在并且创建成功,则为true;如果目标文件存在,则为false * @throws IOException IO异常 */ public static boolean createNewFile(File file) throws IOException { // 目标文件存在,直接返回false. if (file.exists()) { return false; } // 如果目录不存在则创建此父目录 final File parentDir = file.getParentFile(); if (parentDir != null && !parentDir.exists()) { parentDir.mkdirs(); } // 目录有了,直接调用JDK的创建新文件命令 return file.createNewFile(); } public static List readLines(final File file) throws IOException { return readLines(file, Charset.defaultCharset()); } /** * Reads the contents of a file line by line to a List of Strings. * The file is always closed. * * @param file the file to read, must not be {@code null} * @param charset the charset to use, {@code null} means platform default * @return the list of Strings representing each line in the file, never {@code null} * @throws IOException in case of an I/O error * @since 2.3 */ public static List readLines(final File file, final Charset charset) throws IOException { try (InputStream in = openInputStream(file)) { return IOUtils.readLines(in, Charsets.toCharset(charset)); } } /** * Opens a {@link FileInputStream} for the specified file, providing better * error messages than simply calling new FileInputStream(file). *

* At the end of the method either the stream will be successfully opened, * or an exception will have been thrown. *

*

* An exception is thrown if the file does not exist. * An exception is thrown if the file object exists but is a directory. * An exception is thrown if the file exists but cannot be read. *

* * @param file the file to open for input, must not be {@code null} * @return a new {@link FileInputStream} for the specified file * @throws FileNotFoundException if the file does not exist * @throws IOException if the file object is a directory * @throws IOException if the file cannot be read * @since 1.3 */ public static FileInputStream openInputStream(final File file) throws IOException { if (file.exists()) { if (file.isDirectory()) { throw new IOException("File '" + file + "' exists but is a directory"); } if (!file.canRead()) { throw new IOException("File '" + file + "' cannot be read"); } } else { throw new FileNotFoundException("File '" + file + "' does not exist"); } return new FileInputStream(file); } /** * Reads the contents of a file into a String using the default encoding for the VM. * The file is always closed. * * @param file the file to read, must not be {@code null} * @return the file contents, never {@code null} * @throws IOException in case of an I/O error * @since 1.3.1 */ public static String readFileToString(final File file) throws IOException { return readFileToString(file, Charset.defaultCharset()); } /** * Reads the contents of a file into a String. * The file is always closed. * * @param file the file to read, must not be {@code null} * @param charsetName the name of the requested charset, {@code null} means platform default * @return the file contents, never {@code null} * @throws IOException in case of an I/O error * @since 2.3 */ public static String readFileToString(final File file, final Charset charsetName) throws IOException { try (InputStream in = openInputStream(file)) { return IOUtils.toString(in, Charsets.toCharset(charsetName)); } } /** * Reads the contents of a file into a String. * The file is always closed. * * @param file the file to read, must not be {@code null} * @return the file contents, never {@code null} * @throws IOException in case of an I/O error * @since 2.3 */ public static byte[] readFileToByteArray(final File file) throws IOException { try (InputStream in = openInputStream(file)) { return IOUtils.toByteArray(in); } } /** * Reads the contents of a file into a String. The file is always closed. * * @param file the file to read, must not be {@code null} * @param charsetName the name of the requested charset, {@code null} means platform default * @return the file contents, never {@code null} * @throws IOException in case of an I/O error * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io * .UnsupportedEncodingException} in version 2.2 if the encoding is not supported. * @since 2.3 */ public static String readFileToString(final File file, final String charsetName) throws IOException { return readFileToString(file, Charsets.toCharset(charsetName)); } /** * 读取指定名称文件中的文本. * 获取jar包内的资源 * * @param fileName 文件名称 * @return 返回文件中的文本 * @throws IOException 文件不存在 */ public static String getStringFromJar(String fileName) throws IOException { // 通过url获取File的绝对路径 InputStream input = FileUtils.class.getResourceAsStream(fileName); if (input != null) { return StringUtils.readString(input); } else { throw new IOException(fileName + " not exist"); } } /** * 读取指定名称文件中的文本. * 获取jar包内的资源 * * @param fileName 文件名称 * @return 返回文件中的文本 * @throws IOException 文件不存在 */ public static byte[] getByteArrayFromJar(String fileName) throws IOException { // 通过url获取File的绝对路径 // InputStream input = FileUtils.class.getResourceAsStream(fileName); InputStream input = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName); if (input != null) { return IOUtils.toByteArray(input); } else { throw new IOException(fileName + " not exist"); } } /** * 加载类路径下指定名称文件中的文本. 包括jar 里的还是在jar外的resource * * @param fileName 文件名称 * @return 返回文件中的文本 * @throws IOException 文件不存在 */ public static byte[] getByteArrayFromFile(String fileName) throws IOException { byte[] ret; URL url = ClassUtils.getDefaultClassLoader().getResource(fileName); File file = null; if (url != null && StringUtils.equals(url.getProtocol(), URL_PROTOCOL_JAR)) { // file = new File(url.getFile()); ret = getByteArrayFromJar(fileName); } else { file = getFile(fileName); ret = FileUtils.readFileToByteArray(file); } return ret; } } ================================================ FILE: gamioo-common/src/main/java/io/gamioo/common/util/JSONUtils.java ================================================ package io.gamioo.common.util; import com.alibaba.fastjson2.JSONObject; import io.gamioo.common.exception.ServiceException; import org.dom4j.Document; import org.dom4j.Element; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public class JSONUtils { public static JSONObject loadFromXMLFile(String fileName) throws ServiceException { JSONObject ret = new JSONObject(); Document document = XMLUtil.loadFromFile(fileName); Element root = document.getRootElement(); JsonXmlUtil.xml2Json(root, ret); return ret; } } ================================================ FILE: gamioo-common/src/main/java/io/gamioo/common/util/JVMUtil.java ================================================ package io.gamioo.common.util; import java.lang.management.ManagementFactory; import java.util.List; /** * @author Allen Jiang */ public class JVMUtil { public static String getStartArgs() { String ret = ""; List list = ManagementFactory.getRuntimeMXBean().getInputArguments(); for (String e : list) { ret += e + " "; } return ret; } } ================================================ FILE: gamioo-common/src/main/java/io/gamioo/common/util/JsonXmlUtil.java ================================================ package io.gamioo.common.util; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dom4j.Attribute; import org.dom4j.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.StringWriter; import java.util.HashMap; import java.util.List; import java.util.Map; /** * xml 和 json的转换器 * * @author Allen Jiang * @since 1.0.0 */ public class JsonXmlUtil { private static final Logger logger = LogManager.getLogger(JsonXmlUtil.class); private static DocumentBuilderFactory documentBuilderFactory; static { documentBuilderFactory = DocumentBuilderFactory.newInstance(); //XML外部实体注入漏洞 https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=23_5 try { // 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 String FEATURE = "http://apache.org/xml/features/disallow-doctype-decl"; documentBuilderFactory.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"; documentBuilderFactory.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"; documentBuilderFactory.setFeature(FEATURE, false); // Disable external DTDs as well FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd"; documentBuilderFactory.setFeature(FEATURE, false); // and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and // Entity Attacks" documentBuilderFactory.setXIncludeAware(false); documentBuilderFactory.setExpandEntityReferences(false); // And, per Timothy Morgan: "If for some reason support for inline DOCTYPEs are // a requirement, then // ensure the entity settings are disabled (as shown above) and beware that SSRF // attacks // (http://cwe.mitre.org/data/definitions/918.html) and denial // of service attacks (such as billion laughs or decompression bombs via "jar:") // are a risk." // remaining parser logic } catch (Exception e) { logger.error(e.getMessage(), e); } } /** * xml转json * * @param element 元素 * @param json json */ public static void xml2Json(Element element, JSONObject json) { //如果是属性 for (Attribute e : element.attributes()) { json.put(e.getName(), e.getValue()); } List list = element.elements(); for (Element e : list) { JSONObject object = new JSONObject(); xml2Json(e, object); Object obj = json.get(e.getName()); if (obj == null) { json.put(e.getName(), object); } else { JSONArray array = null; if (obj instanceof JSONObject) { JSONObject jsonObject = (JSONObject) obj; json.remove(e.getName()); array = new JSONArray(); array.add(jsonObject); array.add(object); } else if (obj instanceof JSONArray) { array = (JSONArray) obj; array.add(object); } json.put(e.getName(), array); } } } public static boolean isEmpty(String str) { if (str == null || str.trim().isEmpty() || "null".equals(str)) { return true; } return false; } /** * 将Map转换为XML格式的字符串 * * @param store Map类型数据 * @return XML格式的字符串 */ public static String mapToXml(Map store) { try { DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); org.w3c.dom.Document document = documentBuilder.newDocument(); org.w3c.dom.Element root = document.createElement("xml"); document.appendChild(root); for (String key : store.keySet()) { Object obj = store.get(key); String value = ""; if (obj != null) { value = String.valueOf(obj).trim(); } org.w3c.dom.Element filed = document.createElement(key); filed.appendChild(document.createTextNode(value)); root.appendChild(filed); } TransformerFactory tf = TransformerFactory.newInstance(); Transformer transformer = tf.newTransformer(); DOMSource source = new DOMSource(document); transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); StringWriter writer = new StringWriter(); StreamResult result = new StreamResult(writer); transformer.transform(source, result); String output = writer.getBuffer().toString(); // .replaceAll("\n|\r", writer.close(); return output; } catch (Exception e) { logger.error(e.getMessage(), e); } return null; } /** * XML格式字符串转换为Map * * @param strXML XML字符串 * @return XML数据转换后的Map */ public static Map xmlToMap(String strXML) { try { Map data = new HashMap<>(); DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8")); org.w3c.dom.Document doc = documentBuilder.parse(stream); doc.getDocumentElement().normalize(); NodeList nodeList = doc.getDocumentElement().getChildNodes(); for (int idx = 0; idx < nodeList.getLength(); ++idx) { Node node = nodeList.item(idx); if (node.getNodeType() == Node.ELEMENT_NODE) { org.w3c.dom.Element element = (org.w3c.dom.Element) node; data.put(element.getNodeName(), element.getTextContent()); } } stream.close(); return data; } catch (Exception ex) { logger.error(ex.getMessage(), ex); } return null; } } ================================================ FILE: gamioo-common/src/main/java/io/gamioo/common/util/MathUtils.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.common.util; import io.gamioo.common.shape.Point; import java.math.BigDecimal; import java.math.RoundingMode; import java.text.NumberFormat; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 数学计算相关的工具类库. * * @author Allen Jiang * @since 1.0.0 */ public class MathUtils { /** * 一 */ public static final double ONE = 1.0; /** * 百 */ public static final double HUNDRED = 100.0; /** * 千 */ public static final double THOUSAND = 1000.0; /** * 万 */ public static final double TEN_THOUSAND = 1_0000.0; /** * 百万 */ public static final double MILLION = 100_0000.0; /** * 计算两个参数的和,如果相加出现溢出那就返回{@code int}的最大值. *

* 区别于JDK的方法,仅仅认同判定方案,游戏世界,溢出时那就修正一个合理的值,一般调用此方法的游戏逻辑决不能因异常而中断 * * @param x 第一个参数 * @param y 第二个参数 * @return 两个参数的和 * @see Math#addExact(int, int) */ public static int addExact(int x, int y) { try { return Math.addExact(x, y); } catch (ArithmeticException e) { return Integer.MAX_VALUE; } } /** * 计算两个参数的和,如果相加出现溢出那就返回{@code long}的最大值. *

* 区别于JDK的方法,仅仅认同判定方案,游戏世界,溢出时那就修正一个合理的值,一般调用此方法的游戏逻辑决不能因异常而中断 * * @param x 第一个参数 * @param y 第二个参数 * @return 两个参数的和 * @see Math#addExact(long, long) */ public static long addExact(long x, long y) { try { return Math.addExact(x, y); } catch (ArithmeticException e) { return Long.MAX_VALUE; } } /** * 计算两个参数的乘积,如果相乘出现溢出那就返回{@code int}的最大值. *

* 区别于JDK的方法,仅仅认同判定方案,游戏世界,溢出时那就修正一个合理的值,一般调用此方法的游戏逻辑决不能因异常而中断 * * @param x 第一个参数 * @param y 第二个参数 * @return 两个参数的乘积 * @see Math#multiplyExact(int, int) */ public static int multiplyExact(int x, int y) { try { return Math.multiplyExact(x, y); } catch (ArithmeticException e) { return Integer.MAX_VALUE; } } /** * 计算两个参数的乘积,如果相乘出现溢出那就返回{@code long}的最大值. *

* 区别于JDK的方法,仅仅认同判定方案,游戏世界,溢出时那就修正一个合理的值,一般调用此方法的游戏逻辑决不能因异常而中断 * * @param x 第一个参数 * @param y 第二个参数 * @return 两个参数的乘积 * @see Math#multiplyExact(long, long) */ public static long multiplyExact(long x, long y) { try { return Math.multiplyExact(x, y); } catch (ArithmeticException e) { return Long.MAX_VALUE; } } /** * 计算两点(x1,y1)到(x2,y2)的距离. *

* Math.sqrt(|x1-x2|² + |y1-y2|²) * * @param x1 坐标X1 * @param y1 坐标Y1 * @param x2 坐标X2 * @param y2 坐标Y2 * @return 两点的距离 */ public static double distance(int x1, int y1, int x2, int y2) { final double x = Math.abs(x1 - x2); final double y = Math.abs(y1 - y2); return Math.sqrt(x * x + y * y); } /** * 计算两点(x1,y1)到(x2,y2)的距离. *

* Math.sqrt(|x1-x2|² + |y1-y2|²) * * @param x1 坐标X1 * @param y1 坐标Y1 * @param x2 坐标X2 * @param y2 坐标Y2 * @return 两点的距离 */ public static double distance(double x1, double y1, double x2, double y2) { final double x = Math.abs(x1 - x2); final double y = Math.abs(y1 - y2); return Math.sqrt(x * x + y * y); } /** * 计算两点P1(x1,y1)到P2(x2,y2)的距离. *

* Math.sqrt(|x1-x2|² + |y1-y2|²) * * @param p1 坐标1 * @param p2 坐标2 * @return 两点的距离 */ public static double distance(Point p1, Point p2) { return distance(p1.getX(), p1.getY(), p2.getX(), p2.getY()); } /** * 判定两点(x1,y1)和(x2,y2)是否相邻. *

* 可用于两个AOI是否相邻判定 * * @param x1 坐标X1 * @param y1 坐标Y1 * @param x2 坐标X2 * @param y2 坐标Y2 * @return 如果两坐标相邻返回true, 否则返回false */ public static boolean adjacent(int x1, int y1, int x2, int y2) { return Math.abs(x1 - x2) <= 1 && Math.abs(y1 - y2) <= 1; } /** * 判定两点P1(x1,y1)和P2(x2,y2)是否相邻. *

* 可用于两个AOI是否相邻判定 * * @param p1 坐标1 * @param p2 坐标2 * @return 如果两坐标相邻返回true, 否则返回false */ public static boolean adjacent(Point p1, Point p2) { return adjacent(p1.getX(), p1.getY(), p2.getX(), p2.getY()); } /** * 向下取整,并返回int值. * * @param a 一个带有小数的数值 * @return 返回向下取整后的int值 */ public static int floorInt(double a) { return (int) Math.floor(a); } /** * 向下取整,并返回long值. * * @param a 一个带有小数的数值 * @return 返回向下取整后的long值 */ public static long floorLong(double a) { return (long) Math.floor(a); } /** * 向上取整,并返回int值. * * @param a 一个带有小数的数值 * @return 返回向上取整后的int值 */ public static int ceilInt(double a) { return (int) Math.ceil(a); } /** * 向上取整,并返回long值. * * @param a 一个带有小数的数值 * @return 返回向上取整后的long值 */ public static long ceilLong(double a) { return (long) Math.ceil(a); } /** * 4舍5入取整,并返回int值. * * @param a 一个带有小数的数值 * @return 返回向上取整后的int值 */ public static int roundInt(double a) { return (int) Math.round(a); } public static int min(List array) { int ret = Integer.MAX_VALUE; for (int e : array) { if (e < ret) { ret = e; } } return ret; } public static int min(int... array) { int ret = Integer.MAX_VALUE; for (int e : array) { if (e < ret) { ret = e; } } return ret; } public static int max(int... array) { int ret = Integer.MIN_VALUE; for (int e : array) { if (e > ret) { ret = e; } } return ret; } public static int max(List array) { int ret = Integer.MIN_VALUE; for (int e : array) { if (e > ret) { ret = e; } } return ret; } /** * 4舍5入取整,并返回long值. * * @param a 一个带有小数的数值 * @return 返回向上取整后的long值 */ public static long roundLong(double a) { return (long) Math.round(a); } /** * 格式化小数位数的方法. *

* 采用了{@link BigDecimal#setScale(int, RoundingMode)}方式来保留小数位数
* 默认舍入方式为4舍5入, 参考{@link RoundingMode#HALF_UP} * * @param value 原始值 * @param newScale 保留小数位数 * @return 返回要被保留指定小数位数的值. */ public static float formatScale(float value, int newScale) { return formatScale(value, newScale, RoundingMode.HALF_UP); } /** * 格式化小数位数的方法. *

* 采用了{@link BigDecimal#setScale(int, RoundingMode)}方式来保留小数位数 * * @param value 原始值 * @param newScale 保留小数位数 * @param mode 被保留位数后舍入方式,参考{@link RoundingMode} * @return 返回要被保留指定小数位数的值. */ public static float formatScale(float value, int newScale, RoundingMode mode) { return BigDecimal.valueOf(value).setScale(newScale, mode).floatValue(); } /** * 格式化小数位数的方法. *

* 采用了{@link BigDecimal#setScale(int, RoundingMode)}方式来保留小数位数
* 默认舍入方式为4舍5入, 参考{@link RoundingMode#HALF_UP} * * @param value 原始值 * @param newScale 保留小数位数 * @return 返回要被保留指定小数位数的值. */ public static double formatScale(double value, int newScale) { return formatScale(value, newScale, RoundingMode.HALF_UP); } /** * 格式化小数位数的方法. *

* 采用了{@link BigDecimal#setScale(int, RoundingMode)}方式来保留小数位数 * * @param value 原始值 * @param newScale 保留小数位数 * @param mode 被保留位数后舍入方式,参考{@link RoundingMode} * @return 返回要被保留指定小数位数的值. */ public static double formatScale(double value, int newScale, RoundingMode mode) { return BigDecimal.valueOf(value).setScale(newScale, mode).doubleValue(); } /** * N种资源掠夺最优计算方案. *

* 有N种资源,尝试抢其他的部分,但各种资源有一定的比例...
* 使用场景:SLG的城池掠夺资源计算 * * @param 资源类型 * @param resources N种资源(参数选用LinkedHashMap,就是想按顺序优先扣前面的...) * @param max 掠夺的最大值 * @param ratio 掠夺比例建议:比例总和在100以内 * @return 一种最优的掠夺结果 */ public static Map plunder(Map resources, long max, Map ratio) { final Map result = new HashMap<>(resources.size()); final int sum = ratio.values().stream().reduce(0, (a, b) -> a + b); final long step = max >= sum ? max / Math.min(1000, Math.max(10, ratio.values().stream().reduce(0, (a, b) -> a + b))) : max; // 总计要抢的资源量 long total = max; while (total > 0) { // 标识是否还有资源可以抢... boolean flag = false; for (Map.Entry e : resources.entrySet()) { if (e.getValue() <= 0) { continue; } // 只要有一种资源大于0都算还有资源 flag = true; // 比例+小步长随便,让抢出来的资源效果更好些... long selfStep = step * ratio.getOrDefault(e.getKey(), 1) + RandomUtils.nextLong(step); long temp = Math.min(selfStep, total); // 如果资源不足,就以当前有的抢光就好了... if (e.getValue() < temp) { temp = e.getValue(); } // 最终本次抢的值 final long value = temp; e.setValue(e.getValue() - value); total -= temp; result.compute(e.getKey(), (k, v) -> v == null ? value : v + value); // 抢满了就退出啦... if (total <= 0) { break; } } // 没有资源可以抢时,就直接退出了... if (!flag) { break; } } return result; } /** * long类型的数值按比率转化为double类型的值. *

* 由于配置表在转化中精度丢失问题,建议策划配置的是long类型的数值,所以就有了这个转化方法。
* 比如:约定XX列为百分比,那配置50,就是50%,等于0.5 * * @param value long类型的数值 * @param ratio 比率 * @return double类型的值 */ public static double longToDouble(long value, double ratio) { return value / ratio; } /** * long类型的数值以千分比转化为double类型的值. *

* 参考 {@link MathUtils#longToDouble(long, double)} * * @param value long类型的数值 * @return double类型的值 */ public static double permillage(long value) { return MathUtils.longToDouble(value, MathUtils.THOUSAND); } /** * long类型的数值以百分比转化为double类型的值. *

* 参考 {@link MathUtils#longToDouble(long, double)} * * @param value long类型的数值 * @return double类型的值 */ public static double percentage(long value) { return MathUtils.longToDouble(value, MathUtils.HUNDRED); } /**格式化乘百分比 * @param value 待格式化的数值 * @return 格式化后的字符串*/ public static String prettyPercentage(double value){ NumberFormat nf = NumberFormat.getPercentInstance(); nf.setMinimumFractionDigits(2); return nf.format(value); } } ================================================ FILE: gamioo-common/src/main/java/io/gamioo/common/util/MethodUtils.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.common.util; import io.gamioo.common.exception.NoPublicMethodException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; /** * 方法工具类. * * @author Allen Jiang * @since 1.0.0 */ public class MethodUtils { /** * 强制调用一个方法{@link Method}. * * @param target 目标对象 * @param method 要调用的方法 * @param args 方法参数 * @return 返回方法的返回值 */ public static Object invoke(final Object target, final Method method, final Object... args) { if (!method.isAccessible()) { method.setAccessible(true); } try { return method.invoke(target, args); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { throw new RuntimeException("反射调用方式时出现了异常情况...", e); } } /** * 获取指定类的所有方法,包含父类的方法. * * @param klass 指定类 * @return 指定类的方法集合. */ public static List getMethodList(final Class klass) { Set result = new HashSet<>(); for (Class target = klass; target != Object.class; target = target.getSuperclass()) { for (Method method : target.getDeclaredMethods()) { result.add(method); } } return new ArrayList<>(result); } /** * 获取指定类中的指定名称和参数的方法 * * @param klass 类 * @param name 方法名称 * @param parameterTypes 方法参数 * @return 方法 */ public static Method getMethod(Class klass, String name, Class... parameterTypes) { try { return klass.getMethod(name, parameterTypes); } catch (NoSuchMethodException | SecurityException e) { throw new NoPublicMethodException(e.getMessage()); } } /** * 判定指定类是否存在Set方法. * * @param klass 指定类 * @return 如果存在则返回true, 否则返回false. */ public static boolean existSetMethod(Class klass) { for (Class target = klass; target != Object.class; target = target.getSuperclass()) { for (Method method : target.getDeclaredMethods()) { if (method.getName().startsWith("set")) { return true; } } } return false; } } ================================================ FILE: gamioo-common/src/main/java/io/gamioo/common/util/NativeUtils.java ================================================ package io.gamioo.common.util; import org.apache.commons.lang3.SystemUtils; import java.io.*; /** * 用于加载native dll的工具类 */ public class NativeUtils { public static void loadLibrary(String name) throws IOException { String suffix = ""; //TODO 暂时只为两种系统服务 if (SystemUtils.IS_OS_LINUX) { suffix += ".so"; } else { suffix += ".dll"; } try (InputStream inputStream = FileUtils.getInputStream(name + suffix); ByteArrayOutputStream out = new ByteArrayOutputStream()) { byte[] buffer = new byte[1024]; int n = 0; while (-1 != (n = inputStream.read(buffer))) { out.write(buffer, 0, n); } File file = File.createTempFile(name, suffix); try (FileOutputStream fileOutputStream = new FileOutputStream(file)) { fileOutputStream.write(out.toByteArray()); } System.load(file.getAbsolutePath()); } } } ================================================ FILE: gamioo-common/src/main/java/io/gamioo/common/util/RandomUtils.java ================================================ /* * Copyright 2015-2020 gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.common.util; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.ThreadLocalRandom; import java.util.function.ToIntFunction; /** * 随机数相关操作工具类. *

* 本工具类中统一以{@link ThreadLocalRandom}为基础的封装 * * @author Allen Jiang * @since 1.0.0 */ public class RandomUtils { private RandomUtils() { } /** * 返回一个随机Boolean值. * * @return 随机Boolean值 */ public static boolean nextBoolean() { return ThreadLocalRandom.current().nextBoolean(); } /** * 返回一个0到指定区间的随机数字. *

* 0 <= random < bound * * @param bound 最大值(不包含) * @return 返回一个0到指定区间的随机数字 */ public static int nextInt(int bound) { return ThreadLocalRandom.current().nextInt(bound); } /** * 返回一个指定区间的随机数字. *

* origin <= random < bound * * @param origin 最小值(包含) * @param bound 最大值(不包含) * @return 返回一个指定区间的随机数字 */ public static int nextInt(int origin, int bound) { return ThreadLocalRandom.current().nextInt(origin, bound); } /** * 返回一个0到指定区间的随机数字. *

* 0 <= random < bound * * @param bound 最大值(不包含) * @return 返回一个0到指定区间的随机数字 */ public static long nextLong(long bound) { return ThreadLocalRandom.current().nextLong(bound); } /** * 返回一个指定区间的随机数字. *

* origin <= random < bound * * @param origin 最小值(包含) * @param bound 最大值(不包含) * @return 返回一个指定区间的随机数字 */ public static long nextLong(long origin, long bound) { return ThreadLocalRandom.current().nextLong(origin, bound); } /** * 返回一个随机Double值. * * @return 随机Double值 */ public static double nextDouble() { return ThreadLocalRandom.current().nextDouble(); } /** * 判定一次随机事件是否成功. * *

     * 如果rate>=1,则百分百返回true.
* 如果rate<=0,则百分百返回false. *
* * @param rate 成功率 * @return 如果成功返回true, 否则返回false. */ public static boolean isSuccess(float rate) { return ThreadLocalRandom.current().nextFloat() < rate; } /** * 判定一次随机事件是否成功. * *
     * 如果rate>=1,则百分百返回true.
* 如果rate<=0,则百分百返回false. *
* * @param rate 成功率 * @return 如果成功返回true, 否则返回false. */ public static boolean isSuccess(double rate) { return ThreadLocalRandom.current().nextDouble() < rate; } /** * 判定一次百分比的随机事件是否成功. *

* 参数自动转化为百分比单位,就是除100 * *

     * RandomUtils.isSuccessByPercentage(rate) = RandomUtils.isSuccess(rate / 100D)
     * 
* * @param rate 成功率/100D * @return 如果成功返回true, 否则返回false. */ public static boolean isSuccessByPercentage(long rate) { return isSuccess(rate / MathUtils.HUNDRED); } /** * 判定一次千分比的随机事件是否成功. *

* 参数自动转化为千分比单位,就是除1000 * *

     * RandomUtils.isSuccessByPermillage(rate) = RandomUtils.isSuccess(rate / 1000D)
     * 
* * @param rate 成功率/1000D * @return 如果成功返回true, 否则返回false. */ public static boolean isSuccessByPermillage(long rate) { return isSuccess(rate / MathUtils.THOUSAND); } /** * 在指定集合中随机出一个元素. *

* 所以元素无权重的随机. * * @param 要随机集合里的元素类型 * @param list 指定集合 * @return 随机返回集合中的一个元素. */ public static T randomList(List list) { // 没有东东的集合,随机个毛线啊... if (list == null || list.isEmpty()) { return null; } return list.get(nextInt(list.size())); } /** * 从一个List集合中随机出指定数量的元素. *

* * source = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
* random(source, 5) = [5, 3, 6, 7, 2] *
* * @param 要随机集合里的元素类型 * @param source List集合 * @param num 指定数量 * @return 如果源为空或指定数量小于1,则返回空集合,否则随机抽取元素组装新集合并返回 */ @SuppressWarnings("unchecked") public static List randomList(final List source, int num) { // 没有源或要取的数小于1个就直接返回空列表 if (source == null || num < 1) { return Collections.emptyList(); } // 数量刚刚好 if (source.size() <= num) { List result = new ArrayList<>(source); Collections.shuffle(source); return result; } // 随机位,最后一个元素向前移动的方式 Object[] rs = source.toArray(); List result = new ArrayList<>(num); for (int i = 0; i < num; i++) { int index = nextInt(rs.length - i); result.add((T) rs[index]); rs[index] = rs[rs.length - 1 - i]; } return result; } /** * 在指定集合中按权重随机出一个元素. *

* K为元素,如果是自定义对象记得重写HashCode和equals.
* V为权重,机率为V/(sum(All)) * * @param 要随机的元素类型,也是Map的Key * @param data 随机集合 * @return 按权重随机返回集合中的一个元素. */ public static K randomByWeight(Map data) { final int sum = data.values().stream().reduce(0, (a, b) -> a + b); if (sum <= 0) { return randomList(new ArrayList<>(data.keySet())); } final int random = nextInt(sum); int step = 0; for (Map.Entry e : data.entrySet()) { step += e.getValue().intValue(); if (step > random) { return e.getKey(); } } throw new RuntimeException("randomByWeight的实现有Bug:" + random); } /** * 在指定集合中按权重随机出一个元素. *

* 权重,机率为V/(sum(All)) * * @param 要随机的元素类型 * @param data 随机集合 * @param weightFunction 元素中权重方法 * @return 按权重随机返回集合中的一个元素 */ public static T randomByWeight(List data, ToIntFunction weightFunction) { final int sum = data.stream().mapToInt(weightFunction).reduce(0, (a, b) -> a + b); if (sum <= 0) { return randomList(data); } final int random = nextInt(sum); int step = 0; for (T e : data) { step += weightFunction.applyAsInt(e); if (step > random) { return e; } } throw new RuntimeException("randomByWeight的实现有Bug:" + random); } /** * 在指定集合中按权重随机出指定数量个元素. *

* 权重,机率为V/(sum(All)) * * @param 要随机的元素类型 * @param data 随机集合 * @param weightFunction 元素中权重方法 * @param num 指定数量 * @return 按权重随机返回集合中的指定数量个元素 */ public static List randomByWeight(List data, ToIntFunction weightFunction, int num) { if (num <= 0) { return Collections.emptyList(); } final int sum = data.stream().mapToInt(weightFunction).reduce(0, (a, b) -> a + b); if (sum <= 0) { return randomList(data, num); } List result = new ArrayList<>(num); for (int i = 1; i <= num; i++) { final int random = nextInt(sum); int step = 0; for (T e : data) { step += weightFunction.applyAsInt(e); if (step > random) { result.add(e); break; } } } return result; } /** * 区间随机 *

* max - min * * @param min 起始值 * @param max 结束值 * @return 随机数 */ public static int randomBetween(int min, int max) { return ThreadLocalRandom.current().nextInt(max - min + 1) + min; } /** * 以 numerator/denominator的概率随机触发 * * @param numerator 分子 * @param denominator 分母 * @return 是否触发 * @throws IllegalArgumentException 分母为0时抛出异常,无法判断此时期望的return */ public static boolean trigger(int numerator, int denominator) { if (denominator <= 0) { throw new IllegalArgumentException("denominator=" + denominator); } if (numerator <= 0) { return false; } if (numerator > denominator) { return true; } return nextInt(0, denominator) < numerator; } } ================================================ FILE: gamioo-common/src/main/java/io/gamioo/common/util/ResourceUtils.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.common.util; import org.apache.commons.lang3.StringUtils; import java.io.File; import java.io.FileNotFoundException; import java.net.*; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public class ResourceUtils { /** Pseudo URL prefix for loading from the class path: "classpath:" */ public static final String CLASSPATH_URL_PREFIX = "classpath:"; /** URL prefix for loading from the file system: "file:" */ public static final String FILE_URL_PREFIX = "file:"; /** URL prefix for loading from the file system: "jar:" */ public static final String JAR_URL_PREFIX = "jar:"; /** URL protocol for a file in the file system: "file" */ public static final String URL_PROTOCOL_FILE = "file"; /** URL protocol for an entry from a jar file: "jar" */ public static final String URL_PROTOCOL_JAR = "jar"; /** URL protocol for an entry from a zip file: "zip" */ public static final String URL_PROTOCOL_ZIP = "zip"; /** URL protocol for an entry from a WebSphere jar file: "wsjar" */ public static final String URL_PROTOCOL_WSJAR = "wsjar"; /** URL protocol for an entry from a JBoss jar file: "vfszip" */ public static final String URL_PROTOCOL_VFSZIP = "vfszip"; /** URL protocol for a JBoss file system resource: "vfsfile" */ public static final String URL_PROTOCOL_VFSFILE = "vfsfile"; /** URL protocol for a general JBoss VFS resource: "vfs" */ public static final String URL_PROTOCOL_VFS = "vfs"; /** File extension for a regular jar file: ".jar" */ public static final String JAR_FILE_EXTENSION = ".jar"; /** Separator between JAR URL and file path within the JAR: "!/" */ public static final String JAR_URL_SEPARATOR = "!/"; /** * Return whether the given resource location is a URL: * either a special "classpath" pseudo URL or a standard URL. * @param resourceLocation the location String to check * @return whether the location qualifies as a URL * @see #CLASSPATH_URL_PREFIX * @see java.net.URL */ public static boolean isUrl(String resourceLocation) { if (resourceLocation == null) { return false; } if (resourceLocation.startsWith(CLASSPATH_URL_PREFIX)) { return true; } try { new URL(resourceLocation); return true; } catch (MalformedURLException ex) { return false; } } /** * Resolve the given resource location to a {@code java.net.URL}. *

Does not check whether the URL actually exists; simply returns * the URL that the given location would correspond to. * @param resourceLocation the resource location to resolve: either a * "classpath:" pseudo URL, a "file:" URL, or a plain file path * @return a corresponding URL object * @throws FileNotFoundException if the resource cannot be resolved to a URL */ public static URL getURL(String resourceLocation) throws FileNotFoundException { Assert.notNull(resourceLocation, "Resource location must not be null"); if (resourceLocation.startsWith(CLASSPATH_URL_PREFIX)) { String path = resourceLocation.substring(CLASSPATH_URL_PREFIX.length()); ClassLoader cl = ClassUtils.getDefaultClassLoader(); URL url = (cl != null ? cl.getResource(path) : ClassLoader.getSystemResource(path)); if (url == null) { String description = "class path resource [" + path + "]"; throw new FileNotFoundException(description + " cannot be resolved to URL because it does not exist"); } return url; } try { // try URL return new URL(resourceLocation); } catch (MalformedURLException ex) { // no URL -> treat as file path try { return new File(resourceLocation).toURI().toURL(); } catch (MalformedURLException ex2) { throw new FileNotFoundException("Resource location [" + resourceLocation + "] is neither a URL not a well-formed file path"); } } } /** * Resolve the given resource location to a {@code java.io.File}, * i.e. to a file in the file system. *

Does not check whether the file actually exists; simply returns * the File that the given location would correspond to. * @param resourceLocation the resource location to resolve: either a * "classpath:" pseudo URL, a "file:" URL, or a plain file path * @return a corresponding File object * @throws FileNotFoundException if the resource cannot be resolved to * a file in the file system */ public static File getFile(String resourceLocation) throws FileNotFoundException { Assert.notNull(resourceLocation, "Resource location must not be null"); if (resourceLocation.startsWith(CLASSPATH_URL_PREFIX)) { String path = resourceLocation.substring(CLASSPATH_URL_PREFIX.length()); String description = "class path resource [" + path + "]"; ClassLoader cl = ClassUtils.getDefaultClassLoader(); URL url = (cl != null ? cl.getResource(path) : ClassLoader.getSystemResource(path)); if (url == null) { throw new FileNotFoundException(description + " cannot be resolved to absolute file path because it does not exist"); } return getFile(url, description); } try { // try URL return getFile(new URL(resourceLocation)); } catch (MalformedURLException ex) { // no URL -> treat as file path return new File(resourceLocation); } } /** * Resolve the given resource URL to a {@code java.io.File}, * i.e. to a file in the file system. * @param resourceUrl the resource URL to resolve * @return a corresponding File object * @throws FileNotFoundException if the URL cannot be resolved to * a file in the file system */ public static File getFile(URL resourceUrl) throws FileNotFoundException { return getFile(resourceUrl, "URL"); } /** * Resolve the given resource URL to a {@code java.io.File}, * i.e. to a file in the file system. * @param resourceUrl the resource URL to resolve * @param description a description of the original resource that * the URL was created for (for example, a class path location) * @return a corresponding File object * @throws FileNotFoundException if the URL cannot be resolved to * a file in the file system */ public static File getFile(URL resourceUrl, String description) throws FileNotFoundException { Assert.notNull(resourceUrl, "Resource URL must not be null"); if (!URL_PROTOCOL_FILE.equals(resourceUrl.getProtocol())) { throw new FileNotFoundException( description + " cannot be resolved to absolute file path " + "because it does not reside in the file system: " + resourceUrl); } try { return new File(toURI(resourceUrl).getSchemeSpecificPart()); } catch (URISyntaxException ex) { // Fallback for URLs that are not valid URIs (should hardly ever happen). return new File(resourceUrl.getFile()); } } /** * Resolve the given resource URI to a {@code java.io.File}, * i.e. to a file in the file system. * @param resourceUri the resource URI to resolve * @return a corresponding File object * @throws FileNotFoundException if the URL cannot be resolved to * a file in the file system */ public static File getFile(URI resourceUri) throws FileNotFoundException { return getFile(resourceUri, "URI"); } /** * Resolve the given resource URI to a {@code java.io.File}, * i.e. to a file in the file system. * @param resourceUri the resource URI to resolve * @param description a description of the original resource that * the URI was created for (for example, a class path location) * @return a corresponding File object * @throws FileNotFoundException if the URL cannot be resolved to * a file in the file system */ public static File getFile(URI resourceUri, String description) throws FileNotFoundException { Assert.notNull(resourceUri, "Resource URI must not be null"); if (!URL_PROTOCOL_FILE.equals(resourceUri.getScheme())) { throw new FileNotFoundException( description + " cannot be resolved to absolute file path " + "because it does not reside in the file system: " + resourceUri); } return new File(resourceUri.getSchemeSpecificPart()); } /** * Determine whether the given URL points to a resource in the file system, * that is, has protocol "file", "vfsfile" or "vfs". * @param url the URL to check * @return whether the URL has been identified as a file system URL */ public static boolean isFileURL(URL url) { String protocol = url.getProtocol(); return (URL_PROTOCOL_FILE.equals(protocol) || URL_PROTOCOL_VFSFILE.equals(protocol) || URL_PROTOCOL_VFS.equals(protocol)); } /** * Determine whether the given URL points to a resource in a jar file, * that is, has protocol "jar", "zip", "vfszip" or "wsjar". * @param url the URL to check * @return whether the URL has been identified as a JAR URL */ public static boolean isJarURL(URL url) { String protocol = url.getProtocol(); return (URL_PROTOCOL_JAR.equals(protocol) || URL_PROTOCOL_ZIP.equals(protocol) || URL_PROTOCOL_VFSZIP.equals(protocol) || URL_PROTOCOL_WSJAR.equals(protocol)); } /** * Determine whether the given URL points to a jar file itself, * that is, has protocol "file" and ends with the ".jar" extension. * @param url the URL to check * @return whether the URL has been identified as a JAR file URL * @since 4.1 */ public static boolean isJarFileURL(URL url) { return (URL_PROTOCOL_FILE.equals(url.getProtocol()) && url.getPath().toLowerCase().endsWith(JAR_FILE_EXTENSION)); } /** * Extract the URL for the actual jar file from the given URL * (which may point to a resource in a jar file or to a jar file itself). * @param jarUrl the original URL * @return the URL for the actual jar file * @throws MalformedURLException if no valid jar file URL could be extracted */ public static URL extractJarFileURL(URL jarUrl) throws MalformedURLException { String urlFile = jarUrl.getFile(); int separatorIndex = urlFile.indexOf(JAR_URL_SEPARATOR); if (separatorIndex != -1) { String jarFile = urlFile.substring(0, separatorIndex); try { return new URL(jarFile); } catch (MalformedURLException ex) { // Probably no protocol in original jar URL, like "jar:C:/mypath/myjar.jar". // This usually indicates that the jar file resides in the file system. if (!jarFile.startsWith("/")) { jarFile = "/" + jarFile; } return new URL(FILE_URL_PREFIX + jarFile); } } else { return jarUrl; } } /** * Create a URI instance for the given URL, * replacing spaces with "%20" URI encoding first. *

Furthermore, this method works on JDK 1.4 as well, * in contrast to the {@code URL.toURI()} method. * @param url the URL to convert into a URI instance * @return the URI instance * @throws URISyntaxException if the URL wasn't a valid URI * @see java.net.URL#toURI() */ public static URI toURI(URL url) throws URISyntaxException { return toURI(url.toString()); } /** * Create a URI instance for the given location String, * replacing spaces with "%20" URI encoding first. * @param location the location String to convert into a URI instance * @return the URI instance * @throws URISyntaxException if the location wasn't a valid URI */ public static URI toURI(String location) throws URISyntaxException { return new URI(StringUtils.replace(location, " ", "%20")); } /** * Set the {@link URLConnection#setUseCaches "useCaches"} flag on the * given connection, preferring {@code false} but leaving the * flag at {@code true} for JNLP based resources. * @param con the URLConnection to set the flag on */ public static void useCachesIfNecessary(URLConnection con) { con.setUseCaches(con.getClass().getSimpleName().startsWith("JNLP")); } } ================================================ FILE: gamioo-common/src/main/java/io/gamioo/common/util/StringUtils.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.common.util; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * 字符串工具类. * * @author Allen Jiang * @since 1.0.0 */ public class StringUtils { private static Pattern NUMBER_PATTERN = Pattern.compile("[0-9]+"); /** * 空字符串 {@code ""}. */ public static final String EMPTY = ""; /** * 一个空格字符串 {@code " "} */ public static final String SPACE = " "; /** * 一个换行字符串 {@code "\n"} */ public static final String LF = "\n"; /** * 一个回车字符串 {@code "\r"} */ public static final String CR = "\r"; /** * 一个英文逗号字符串 {@code ","} */ public static final String COMMA = ","; /** * 一个英文连字符字符串 {@code "-"} */ public static final String HYPHEN = "-"; /** * 一个英文左括号字符串 {@code "("} */ public static final String LPAREN = "("; /** * 一个英文右括号字符串 {@code ")"} */ public static final String RPAREN = ")"; /** * 一个英文左大括号字符串 "{" */ public static final String LBRACE = "{"; /** * 一个英文右大括号字符串 "}" */ public static final String RBRACE = "}"; /** * 一个英文左中括号字符串 {@code "["} */ public static final String LBRACKET = "["; /** * 一个英文右中括号字符串 {@code "]"} */ public static final String RBRACKET = "]"; /** * 一个英文冒号字符串 {@code ":"} */ public static final String COLON = ":"; /** * 一个英文星号字符串 {@code "*"} */ public static final String ASTERISK = "*"; /** * 一个空字符串数组. */ public static final String[] EMPTY_STRING_ARRAY = {}; /** * 检测字符串是否为 null或"". * *

     * StringUtils.isEmpty(null)      = true
     * StringUtils.isEmpty("")        = true
     * StringUtils.isEmpty(" ")       = false
     * StringUtils.isEmpty("test")     = false
     * StringUtils.isEmpty("  test  ") = false
     * 
* * @param text 被检测字符串 * @return 如果字符为null或""则返回true,否则返回false. */ public static boolean isEmpty(final String text) { return text == null || text.length() == 0; } /** * 检测字符串是否不为 null且不为"". * *
     * StringUtils.isNotEmpty(null)      = false
     * StringUtils.isNotEmpty("")        = false
     * StringUtils.isNotEmpty(" ")       = true
     * StringUtils.isNotEmpty("test")    = true
     * StringUtils.isNotEmpty("  test ") = true
     * 
* * @param text 被检测字符串 * @return 如果字符不为 null且不为""则返回true,否则返回false. */ public static boolean isNotEmpty(final String text) { return !isEmpty(text); } /** * 检测字符串是否为空,null或Java空白字符。 *

* Java空白字符的定义请参考{@link Character#isWhitespace(char)}. * *

     * StringUtils.isBlank(null)      = true
     * StringUtils.isBlank("")        = true
     * StringUtils.isBlank(" ")       = true
     * StringUtils.isBlank("bob")     = false
     * StringUtils.isBlank("  bob  ") = false
     * 
* * @param text 被检测字符串 * @return 如果字符串为空,null或Java空白字符则返回true,否则返回false. */ public static boolean isBlank(final String text) { if (text == null) { return true; } // 只要有一个字符不为空白,那就肯定不是空白 for (int i = 0, len = text.length(); i < len; i++) { if (!Character.isWhitespace(text.charAt(i))) { return false; } } return true; } /** * 检测字符串是否不为空,null或Java空白字符。 *

* Java空白字符的定义请参考{@link Character#isWhitespace(char)}. * *

     * StringUtils.isNotBlank(null)      = false
     * StringUtils.isNotBlank("")        = false
     * StringUtils.isNotBlank(" ")       = false
     * StringUtils.isNotBlank("bob")     = true
     * StringUtils.isNotBlank("  bob  ") = true
     * 
* * @param text 被检测字符串 * @return 如果字符串不为空,null或Java空白字符则返回true,否则返回false. */ public static boolean isNotBlank(final String text) { return !isBlank(text); } /** * 检测一个字符串长度. *

* 字符串有可能包含中文等其他文字,中文应该算2个长度. * * @param text 被检测字符串 * @return 字符串长度 */ public static int length(final String text) { if (text == null) { return 0; } int sum = 0; for (int i = 0, len = text.length(); i < len; i++) { sum += text.charAt(i) > 127 ? 2 : 1; } return sum; } /** *

* Splits the provided text into an array, separators specified. This is an alternative to using StringTokenizer. *

* *

* The separator is not included in the returned String array. Adjacent separators are treated as one separator. For more control over the split use the StrTokenizer class. *

* *

* A {@code null} input String returns {@code null}. A {@code null} separatorChars splits on whitespace. *

* *
     * StringUtils.split(null, *)         = null
     * StringUtils.split("", *)           = []
     * StringUtils.split("abc def", null) = ["abc", "def"]
     * StringUtils.split("abc def", " ")  = ["abc", "def"]
     * StringUtils.split("abc  def", " ") = ["abc", "def"]
     * StringUtils.split("ab:cd:ef", ":") = ["ab", "cd", "ef"]
     * 
* * @param str the String to parse, may be null * @param separatorChars the characters used as the delimiters, {@code null} splits on whitespace * @return an array of parsed Strings, {@code null} if null String input */ public static String[] split(final String str, final String separatorChars) { return splitWorker(str, separatorChars, -1, false); } /** * Performs the logic for the {@code split} and {@code splitPreserveAllTokens} methods that return a maximum array length. * * @param str the String to parse, may be {@code null} * @param separatorChars the separate character * @param max the maximum number of elements to include in the array. A zero or negative value implies no limit. * @param preserveAllTokens if {@code true}, adjacent separators are treated as empty token separators; if {@code false}, adjacent separators are treated as one separator. * @return an array of parsed Strings, {@code null} if null String input */ private static String[] splitWorker(final String str, final String separatorChars, final int max, final boolean preserveAllTokens) { if (str == null) { return EMPTY_STRING_ARRAY; } final int len = str.length(); if (len == 0) { return EMPTY_STRING_ARRAY; } final List list = new ArrayList<>(); int sizePlus1 = 1; int i = 0, start = 0; boolean match = false; boolean lastMatch = false; if (separatorChars == null) { while (i < len) { if (Character.isWhitespace(str.charAt(i))) { if (match || preserveAllTokens) { lastMatch = true; if (sizePlus1++ == max) { i = len; lastMatch = false; } list.add(str.substring(start, i)); match = false; } start = ++i; continue; } lastMatch = false; match = true; i++; } } else if (separatorChars.length() == 1) { final char sep = separatorChars.charAt(0); while (i < len) { if (str.charAt(i) == sep) { if (match || preserveAllTokens) { lastMatch = true; if (sizePlus1++ == max) { i = len; lastMatch = false; } list.add(str.substring(start, i)); match = false; } start = ++i; continue; } lastMatch = false; match = true; i++; } } else { while (i < len) { if (separatorChars.indexOf(str.charAt(i)) >= 0) { if (match || preserveAllTokens) { lastMatch = true; if (sizePlus1++ == max) { i = len; lastMatch = false; } list.add(str.substring(start, i)); match = false; } start = ++i; continue; } lastMatch = false; match = true; i++; } } final boolean flag = preserveAllTokens && lastMatch; if (match || flag) { list.add(str.substring(start, i)); } return list.toArray(new String[0]); } /** * 将一个字符串由驼峰式命名变成分割符分隔单词 * *
     *  lowerWord("helloWorld", '_') => "hello_world"
     * 
* * @param cs 字符串 * @param c 分隔符 * @return 转换后字符串 */ public static String lowerWord(CharSequence cs, char c) { int len = cs.length(); StringBuilder sb = new StringBuilder(len + 5); for (int i = 0; i < len; i++) { char ch = cs.charAt(i); if (Character.isUpperCase(ch)) { if (i > 0) { sb.append(c); } sb.append(Character.toLowerCase(ch)); } else { sb.append(ch); } } return sb.toString(); } /** * 计算一个Long类型的数字的文本长度. *

* 包含负数计算 * * @param v Long类型的数字 * @return 数字的文本长度 */ public static int asciiSizeInBytes(long v) { if (v == 0) { return 1; } if (v == Long.MIN_VALUE) { return 20; } boolean negative = false; if (v < 0) { v = -v; negative = true; } int width = v < 100000000L // ? v < 10000L // ? v < 100L ? v < 10L ? 1 : 2 : v < 1000L ? 3 : 4 // : v < 1000000L ? v < 100000L ? 5 : 6 : v < 10000000L ? 7 : 8 // : v < 1000000000000L // ? v < 10000000000L ? v < 1000000000L ? 9 : 10 : v < 100000000000L ? 11 : 12 // : v < 1000000000000000L // ? v < 10000000000000L ? 13 : v < 10000000000000L ? 14 : 15 // : v < 100000000000000000L // ? v < 10000000000000000L ? 16 : 17 // : v < 1000000000000000000L ? 18 : 19; // return negative ? width + 1 : width; } /** * 拼接字符串. * * @param strings 需要拼接的字串 * @return 拼接后的字符串 */ public static String join(String... strings) { int len = 0; for (String str : strings) { len += str.length(); } final StringBuilder result = new StringBuilder(len); for (String str : strings) { result.append(str); } return result.toString(); } /** * 拼接路径字符串. * * @param paths 需要拼接的路径字串 * @return 拼接后的路径字符串 */ public static String pathJoin(String... paths) { int len = 0; for (String str : paths) { len += str.length() + 1; } final StringBuilder sb = new StringBuilder(len); for (String str : paths) { sb.append(str); if (sb.length() > 0 && sb.charAt(sb.length() - 1) != '/') { sb.append('/'); } } return sb.toString(); } /** * 拼接字符串. *

* 在比较长或多的情况计算长度比StringJoiner性能好.
* *

     *     StringJoiner result = new StringJoiner(delimiter, prefix, suffix);
     *     for (String str : strings) {
     *         result.add(str);
     *    }
     *     return result.toString();
     *
     *     Stream.of(strings).collect(Collectors.joining(delimiter, prefix, suffix))
     * 
* * @param delimiter 分隔符 * @param prefix 前缀 * @param suffix 后缀 * @param strings 需要拼接的字串 * @return 拼接后的字符串 */ public static String build(String delimiter, String prefix, String suffix, String... strings) { int len = prefix.length() + suffix.length() + (strings.length - 1) * delimiter.length(); for (String str : strings) { len += str.length(); } StringBuilder result = new StringBuilder(len); result.append(prefix); for (String str : strings) { result.append(str).append(delimiter); } if (result.length() > prefix.length()) { result.deleteCharAt(result.length() - 1); } result.append(suffix); return result.toString(); } /** * 编码字符串,编码为UTF-8 * * @param str 字符串 * @return 编码后的字节数组 */ public static byte[] utf8Bytes(CharSequence str) { return bytes(str, CharsetUtils.CHARSET_UTF_8); } /** * 编码字符串
* 使用系统默认编码 * * @param str 字符串 * @return 编码后的字节码 */ public static byte[] bytes(CharSequence str) { return bytes(str, null); } /** * 编码字符串 * * @param str 字符串 * @param charset 字符集,如果此字段为空,则编码的结果取决于平台 * @return 编码后的字节数组 */ public static byte[] bytes(CharSequence str, Charset charset) { if (str == null) { return ByteArrayUtils.EMPTY_BYTE_ARRAY; } if (null == charset) { return str.toString().getBytes(); } return str.toString().getBytes(charset); } // /** // * 常规的格式化一个带有占位符的字符串. // *

// * 区别于JDK的{@link MessageFormat#format(String, Object...)},这个方法只是简单的按位填充 // * // * @param messagePattern 带有占位符的字符串 // * @param arguments 参数列表 // * @return 格式化以后的字符串 // */ // public static String format(String messagePattern, Object... arguments) { // if (arguments.length == 0) { // return messagePattern; // } // // FIXME 这个方法基本用于日志,邮件内容拼接,可以参考日志中使用的方案做一个缓存策略 // for (int i = 0, len = arguments.length; i < len; i++) { // messagePattern = messagePattern.replace(join("{", String.valueOf(i), "}"), String.valueOf(arguments[i])); // } // return messagePattern; // } /** * Replace placeholders in the given messagePattern with arguments. * * @param messagePattern the message pattern containing placeholders. * @param arguments the arguments to be used to replace placeholders. * @return the formatted message. */ public static String format(String messagePattern, Object... arguments) { if (messagePattern == null || arguments == null || arguments.length == 0) { return messagePattern; } final StringBuilder result = new StringBuilder(); int currentArgument = 0; for (int i = 0; i < messagePattern.length(); i++) { final char curChar = messagePattern.charAt(i); if (curChar == DELIM_START && i < messagePattern.length() - 1 && messagePattern.charAt(i + 1) == DELIM_STOP) { if (currentArgument < arguments.length) { result.append(arguments[currentArgument]); } else { result.append(DELIM_START).append(DELIM_STOP); } currentArgument++; i++; } else { result.append(curChar); } } return result.toString(); } /** * 从输入流中读出所有文本. * * @param inputStream 输入流 * @return 返回流中的文本 * @throws IOException If an I/O error occurs */ public static String readString(InputStream inputStream) throws IOException { return readString(inputStream, CharsetUtils.CHARSET_UTF_8); } /** * 从输入流中读出所有文本. * * @param inputStream 输入流 * @param charset 文本的编码方式 * @return 返回流中的文本 * @throws IOException If an I/O error occurs */ public static String readString(InputStream inputStream, Charset charset) throws IOException { try (InputStreamReader isr = new InputStreamReader(inputStream, charset)) { return readString(isr); } } /** * 读出所有文本。 *

* 这里没有选择BufferedReader就是不想一行一行的读,浪费字符串拼接性能
* 正常用于读HTTP的响应,配置文件内容,小文件等情况 * * @param reader 抽象的文本流 * @return 返回流中所有文本 * @throws IOException If an I/O error occurs */ public static String readString(Reader reader) throws IOException { final StringBuilder sb = new StringBuilder(1024); // 申明一次读取缓冲区 final char[] array = new char[256]; // 这里并没有使用while(true),如果一个文本超过100W,还是放弃后面的算了 while (sb.length() < MathUtils.MILLION) { int n = reader.read(array); // 读结束了,就GG了 if (n < 0) { break; } sb.append(array, 0, n); } return sb.toString(); } /** * 从左边使用空格(' ')补齐指定位位数的字符串. *

     * StringUtils.leftPad(null, *)   = null
     * StringUtils.leftPad("", 3)     = "   "
     * StringUtils.leftPad("bat", 3)  = "bat"
     * StringUtils.leftPad("bat", 5)  = "  bat"
     * StringUtils.leftPad("bat", 1)  = "bat"
     * StringUtils.leftPad("bat", -1) = "bat"
     * 
* * @param str 字符串 * @param size 补齐后的位数 * @return 返回补齐后的字符串{@code null} */ public static String leftPad(final String str, final int size) { return leftPad(str, size, ' '); } /** * 从左边使用指定字符补齐指定位位数的字符串. * *
     * StringUtils.leftPad(null, *, *)     = null
     * StringUtils.leftPad("", 3, 'b')     = "bbb"
     * StringUtils.leftPad("bat", 3, 'b')  = "bat"
     * StringUtils.leftPad("bat", 5, ' ')  = "  bat"
     * StringUtils.leftPad("bat", 1, 'b')  = "bat"
     * StringUtils.leftPad("bat", -1, 'b') = "bat"
     * 
* * @param str 字符串 * @param size 补齐后的位数 * @param padChar 补齐字符 * @return 返回补齐后的字符串{@code null} */ public static String leftPad(final String str, final int size, final char padChar) { if (str == null) { return null; } // 不需要补位,那就返回原来的字符串 final int pads = size - str.length(); if (pads <= 0) { return str; } // 创建返回数组,前面填充指定字符,后面复制原来的字符串 final char[] array = new char[size]; Arrays.fill(array, 0, pads, padChar); System.arraycopy(str.toCharArray(), 0, array, pads, str.length()); return new String(array); } private static final String FOLDER_SEPARATOR = "/"; private static final char DELIM_START = '{'; private static final char DELIM_STOP = '}'; private static final int GB = 1024 * 1024 * 1024;// 定义GB的计算常量 private static final int MB = 1024 * 1024;// 定义MB的计算常量 private static final int KB = 1024;// 定义KB的计算常量 public static boolean isEmpty(String... array) { for (String str : array) { if (org.apache.commons.lang3.StringUtils.isEmpty(str)) { return true; } } return false; } public static String format2Percent(double value) { String p = String.valueOf(value * 100D); int ix = p.indexOf(".") + 1; String percent = p.substring(0, ix) + p.substring(ix, ix + 1); return percent + "%"; } public static String byte2GBMBKB(long value) { if (value / GB >= 1) // 如果当前Byte的值大于等于1GB { return String.format("%.2f", value / (float) GB) + " GB";// 将其转换成GB } else if (value / MB >= 1) // 如果当前Byte的值大于等于1MB { return String.format("%.2f", value / (float) MB) + " MB";// 将其转换成MB } else if (value / KB >= 1) // 如果当前Byte的值大于等于1KB { return String.format("%.2f", value / (float) KB) + " KB";// 将其转换成KB } else { return String.valueOf(value) + " Byte";// 显示Byte值 } } public static String stringToAscii(String value) { StringBuffer ret = new StringBuffer(); char[] chars = value.toCharArray(); for (int i = 0; i < chars.length; i++) { if (i != chars.length - 1) { ret.append((int) chars[i]); } else { ret.append((int) chars[i]); } } return ret.toString(); } /** * 解析字符串中所有数字,注意:这里只是把数字字符串切割出来了,转换成数字时注意边界 * * @param content 需要解析的字符串 * @return 所有数字字符串集合 */ public static ArrayList incisionNumber(String content) { ArrayList ret = new ArrayList<>(); if (org.apache.commons.lang3.StringUtils.isEmpty(content)) { return ret; } Matcher m = NUMBER_PATTERN.matcher(content); while (m.find()) { ret.add(m.group()); } return ret; } /** * 判断字符串是否是同一个字符。kkk全是k;22222全是2 * * @param arg 需要解析的字符串 * @return 返回判断结果 */ public static boolean isSameChar(String arg) { boolean flag = true; if (org.apache.commons.lang3.StringUtils.isEmpty(arg)) { return flag; } char str = arg.charAt(0); for (int i = 1; i < arg.length(); i++) { if (str != arg.charAt(i)) { flag = false; break; } } return flag; } /** * 判断一个数字是否是连续的数字,例如,12345和54321就是连续的 * * @param str 要判断的数字字符串 * @return 返回结果 */ public static boolean strNumberIsContinue(String str) { if (isEmpty(str)) { return false; } char[] chars = str.toCharArray(); char c = chars[0]; for (int i = 1; i < chars.length; i++) { int diff = chars[i] - c; if (diff != 1 && diff != -1) { return false; } c = chars[i]; } return true; } /** * 获取字符串中去重后剩下多少个字符 * * @param str 需要解析的字符串 * @return 返回去重后的字符串 */ public static int getStringCharCount(String str) { if (isEmpty(str)) { return 0; } HashSet set = new HashSet<>(); for (char b : str.toCharArray()) { set.add(b); } return set.size(); } public static boolean hasLength(String str) { return hasLength((CharSequence) str); } public static boolean hasLength(CharSequence str) { return (str != null && str.length() > 0); } public static boolean hasText(CharSequence str) { if (!hasLength(str)) { return false; } int strLen = str.length(); for (int i = 0; i < strLen; i++) { if (!Character.isWhitespace(str.charAt(i))) { return true; } } return false; } /** * Check whether the given {@code String} contains actual text. *

More specifically, this method returns {@code true} if the * {@code String} is not {@code null}, its length is greater than 0, * and it contains at least one non-whitespace character. * * @param str the {@code String} to check (may be {@code null}) * @return {@code true} if the {@code String} is not {@code null}, its * length is greater than 0, and it does not contain whitespace only * @see #hasText(CharSequence) */ public static boolean hasText(String str) { return hasText((CharSequence) str); } /** * Apply the given relative path to the given path, * assuming standard Java folder separation (i.e. "/" separators). * * @param path the path to start from (usually a full file path) * @param relativePath the relative path to apply * (relative to the full file path above) * @return the full file path that results from applying the relative path */ public static String applyRelativePath(String path, String relativePath) { int separatorIndex = path.lastIndexOf(FOLDER_SEPARATOR); if (separatorIndex != -1) { String newPath = path.substring(0, separatorIndex); if (!relativePath.startsWith(FOLDER_SEPARATOR)) { newPath += FOLDER_SEPARATOR; } return newPath + relativePath; } else { return relativePath; } } public static String uncapitalized(String name) { if (name == null || name.length() == 0) { return name; } if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) && Character.isUpperCase(name.charAt(0))) { return name; } char chars[] = name.toCharArray(); chars[0] = Character.toLowerCase(chars[0]); return new String(chars); } public static boolean equals(final CharSequence cs1, final CharSequence cs2) { if (cs1 == cs2) { return true; } if (cs1 == null || cs2 == null) { return false; } if (cs1.length() != cs2.length()) { return false; } if (cs1 instanceof String && cs2 instanceof String) { return cs1.equals(cs2); } // Step-wise comparison final int length = cs1.length(); for (int i = 0; i < length; i++) { if (cs1.charAt(i) != cs2.charAt(i)) { return false; } } return true; } } ================================================ FILE: gamioo-common/src/main/java/io/gamioo/common/util/TelnetUtils.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.common.util; import org.apache.commons.net.telnet.TelnetClient; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public class TelnetUtils { private static final Logger logger = LogManager.getLogger(TelnetUtils.class); /** * 模拟Telnet 连接 * * @param checkNum 验证几个 * @param ip 地址 * @param port 端口 * @return 返回是否连接成功 *

* 检测是否能连上 */ public static boolean isConnected(int checkNum, String ip, int port) { boolean ret = false; for (int i = 0; i < checkNum; i++) { try { TelnetClient telnet = new TelnetClient(); telnet.connect(ip, port); ret = telnet.isConnected(); telnet.disconnect(); } catch (Exception e) { logger.error(e.getMessage(), e); } if (ret) { return ret; } else { try { Thread.sleep(5000); } catch (InterruptedException e) { logger.error(e.getMessage(), e); } } } return ret; } public static boolean isConnected(String ip, int port) { boolean ret = false; try { TelnetClient telnet = new TelnetClient(); telnet.setConnectTimeout(5000);//连接超时时间 // TelnetClient telnetClient = new TelnetClient("vt200"); //指明Telnet终端类型,否则会返回来的数据中文会乱码 // telnet.setDefaultTimeout(5000);//打开端口的超时时间 telnet.connect(ip, port); ret = telnet.isConnected(); if (ret) { logger.info("该连接可以用 {}:{}", ip, port); telnet.disconnect(); } } catch (Exception e) { // logger.warn("该连接无法使用 {}:{}",ip,port); // logger.error(e.getMessage(), e); } return ret; } } ================================================ FILE: gamioo-common/src/main/java/io/gamioo/common/util/ThreadUtils.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.common.util; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; /** * 线程工具类. * * @author Allen Jiang * @since 1.0.0 */ public class ThreadUtils { private static final Logger logger = LogManager.getLogger(ThreadUtils.class); /** * 暂停执行. *

* 就是JDK的{@link Thread#sleep(long)}包装一下就不用管这个异常了.
* 这个方法只用于写一些测试用例时使用... * * @param millis 暂停毫秒数 */ public static void sleep(long millis) { try { if (millis > 0) { Thread.sleep(millis); } } catch (InterruptedException e) { throw new RuntimeException(e); } } /** * 输出当前线程正在运行的堆栈信息. * * @param thread 当前线程 * @return 当前线程正在运行的堆栈信息 */ public static String printStackTrace(Thread thread) { final StackTraceElement[] st = thread.getStackTrace(); StringBuffer sb = new StringBuffer(2048); sb.append("\n"); for (StackTraceElement e : st) { sb.append("\tat ").append(e).append("\n"); } return sb.toString(); } /** * 输出当前线程正在运行的堆栈信息. * 当前线程正在运行的堆栈信息 */ public static void printStackTrace() { try { throw new Exception(); } catch (Exception e) { logger.error(e.getMessage(), e); } } } ================================================ FILE: gamioo-common/src/main/java/io/gamioo/common/util/XMLUtil.java ================================================ package io.gamioo.common.util; import io.gamioo.common.exception.ServiceException; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.DocumentHelper; import org.dom4j.Element; import org.dom4j.io.OutputFormat; import org.dom4j.io.SAXReader; import org.dom4j.io.XMLWriter; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.rmi.ServerException; /** * @author Guilong Jiang */ public class XMLUtil { public static boolean saveXML(Element ele, String sFilePathName) throws ServerException { return saveXML(ele, sFilePathName, "UTF-8"); } /** * save xml to file with special encode * * @param ele 元素 * @param sFilePathName 文件路径 * @param encode XMLUtil.ENCODE_UTF_8 or XMLUtil.ENCODE_GBK, default is * former * @return 保存成功与否 */ public static boolean saveXML(Element ele, String sFilePathName, String encode) throws ServiceException { Document dom = ele.getDocument(); if (dom == null) { dom = DocumentHelper.createDocument(ele); } return saveXML(dom, sFilePathName, encode); } /** * save xml to file with special encode * * @param dom 节点 * @param sFilePathName 文件路径 * @param encode XMLUtil.ENCODE_UTF_8 or XMLUtil.ENCODE_GBK, default is * former * @return 保存成功与否 */ public static boolean saveXML(Document dom, String sFilePathName, String encode) throws ServiceException { File file = new File(sFilePathName); File parent = file.getParentFile(); if (!parent.exists()) { parent.mkdirs(); } try { OutputFormat format = OutputFormat.createPrettyPrint(); FileOutputStream out = new FileOutputStream(file); // if(!encode.equals(ENCODE_UTF_8)){ format.setEncoding(encode); XMLWriter xmlWriter = new XMLWriter(out, format); xmlWriter.write(dom); xmlWriter.flush(); xmlWriter.close(); } catch (Exception e) { e.printStackTrace(); throw new ServiceException("XUT-PSX10003", "write element to file error:{} {}", sFilePathName, e.getMessage()); } return true; } public static Document loadFromFile(String filePathName) throws ServiceException { InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(filePathName); return loadDocument(is); } public static Document loadDocument(InputStream is) throws ServiceException { SAXReader rd = new SAXReader(); Document document = null; try { document = rd.read(is); } catch (Exception e) { throw new ServiceException("parsing document failed:" + e.getMessage()); } return document; } public static int attributeValueInt(Element element, String attr) throws ServiceException { if (element == null) { throw new ServiceException("element == null"); } String value = element.attributeValue(attr); if (value == null) { throw new ServiceException("缺少属性{}: {}", attr, element.asXML()); } try { return Integer.parseInt(value); } catch (NumberFormatException e) { throw new ServiceException("属性{}不是数值类型: {}", attr, element.asXML()); } } public static float attributeValueFloat(Element element, String attr) throws ServiceException { if (element == null) { throw new ServiceException("element == null"); } String value = element.attributeValue(attr); if (value == null) { throw new ServiceException("缺少属性{}: {}", attr, element.asXML()); } try { return Float.parseFloat(value); } catch (NumberFormatException e) { throw new ServiceException("属性{}不是数值类型: {}", attr, element.asXML()); } } public static int attributeValueInt(Element element, String attr, int defaultValue) throws ServiceException { if (element == null) { throw new ServiceException("element == null"); } String value = element.attributeValue(attr); if (value == null) { return defaultValue; } try { return Integer.parseInt(value); } catch (NumberFormatException e) { throw new ServiceException("属性{}不是数值类型: {}", attr, element.asXML()); } } public static String attributeValueString(Element element, String attr) throws ServiceException { if (element == null) { throw new ServiceException("element == null"); } String value = element.attributeValue(attr); if (value == null) { throw new ServiceException("缺少属性{}: element={}", attr, element.asXML()); } return value; } public static String attributeValueString(Element element, String attr, String defaultValue) throws ServiceException { if (element == null) { throw new ServiceException("element == null"); } String value = element.attributeValue(attr); if (value == null) { return defaultValue; } return value; } public static boolean attributeValueBoolean(Element element, String attr, boolean defaultValue) throws ServiceException { if (element == null) { throw new ServiceException("element == null"); } String value = element.attributeValue(attr); if (value == null) { return defaultValue; } try { return Boolean.parseBoolean(value); } catch (NumberFormatException e) { throw new ServiceException("属性{}不是布尔类型: {}", attr, element.asXML()); } } public static boolean attributeValueBoolean(Element element, String attr) throws ServiceException { if (element == null) { throw new ServiceException("element == null"); } String value = element.attributeValue(attr); if (value == null) { throw new ServiceException("缺少属性{}: {}", attr, element.asXML()); } try { return Boolean.parseBoolean(value); } catch (NumberFormatException e) { throw new ServiceException("属性{}不是布尔类型: {}", attr, element.asXML()); } } public static Element subElement(Element parent, String name) throws ServiceException { if (parent == null) { throw new ServiceException("parent == null"); } Element result = parent.element(name); if (result == null) { throw new ServiceException("找不到{}节点的子节点{}", parent.getName(), name); } return result; } public static Element parseText(String content) { Element ret = null; try { Document doc = DocumentHelper.parseText(content); ret = doc.getRootElement(); // 获取根节点 } catch (DocumentException e) { throw new ServiceException("parsing content failed:{}", e.getMessage()); } return ret; } } ================================================ FILE: gamioo-common/src/main/java/io/gamioo/common/vector/Vector2f.java ================================================ package io.gamioo.common.vector; import io.gamioo.common.shape.Point; /** * @author Allen Jiang */ public class Vector2f { private final float x; private final float y; public Vector2f(float x, float y) { this.x = x; this.y = y; } public float getX() { return x; } public float getY() { return y; } public double getRadians() { return Math.atan2(y, x); } public Point toPoint() { return Point.valueOf(x, y); } public double getAngle() { return Math.toDegrees(this.getRadians()); } public double getLength() { return Math.sqrt(x * x + y * y); } public static Vector2f valueOf(int length, int angle) { double radians = Math.toRadians(angle); float y = (float) Math.sin(radians) * length; float x = (float) Math.cos(radians) * length; return new Vector2f(x, y); } public static Vector2f valueOf(float x, float y) { return new Vector2f(x, y); } public static Vector2f add(Vector2f a, Vector2f b) { return new Vector2f(a.x + b.x, a.y + b.y); } public Vector2f add(Vector2f src) { return new Vector2f(x + src.x, y + src.y); } public Point add(Point srcPoint) { return Point.valueOf(x + srcPoint.getX(), y + srcPoint.getY()); } /** * 单位化 * * @param a 向量 * @return 调整后的向量 */ public static Vector2f unitization(Vector2f a) { float len = (float) a.getLength(); return new Vector2f(a.getX() / len, a.getY() / len); } public Vector2f unitization() { return unitization(this); } /** * 调整长度 * * @param newLength 新长度 * @return 调整后的向量 */ public Vector2f resizeLength(float newLength) { float len = (float) getLength(); return Vector2f.valueOf(x / len * newLength, y / len * newLength); } /** * 旋转 * * @param angle 顺时针 旋转角度 [0 - 360] * @return 旋转后的向量 */ public Vector2f rotate(int angle) { double radians = Math.toRadians(angle); double sin = Math.sin(radians); double cos = Math.cos(radians); return Vector2f.valueOf((float) (sin * y + cos * x), (float) (cos * y - sin * x)); } public Vector2f rotate(double radians) { double sin = Math.sin(radians); double cos = Math.cos(radians); return Vector2f.valueOf((float) (sin * y + cos * x), (float) (cos * y - sin * x)); } /** * 绕着point 旋转 radian弧度后得到的点 * * @param point 点 * @param radians 弧度 * @return 旋转后的向量 */ public Vector2f rotate(Vector2f point, double radians) { double sin = Math.sin(radians); double cos = Math.cos(radians); int x = (int) ((this.x - point.getX()) * cos - (this.y - point.getY()) * sin + point.getX()); int y = (int) ((this.x - point.getX()) * sin + (this.y - point.getY()) * cos + point.getY()); return Vector2f.valueOf(x, y); } public static Vector2f getVectorFromPointToPoint(Point srcPoint, Point endPoint) { return new Vector2f(endPoint.getX() - srcPoint.getX(), endPoint.getY() - srcPoint.getY()); } public static Vector2f getVectorFromPointToPoint(int srcX, int srcY, int endX, int endY) { return new Vector2f(endX - srcX, endY - srcY); } /** * 向量点乘 * * @param a 向量1 * @param b 向量2 * @return 返回结果 */ public static float dotProduct(Vector2f a, Vector2f b) { return a.getX() * b.getX() + a.getY() * b.getY(); } /** * 向量叉乘 * * @param a 向量1 * @param b 向量2 * @return 返回结果 */ public static float crossProduct(Vector2f a, Vector2f b) { return a.getX() * b.getY() - a.getY() * b.getX(); } /** * 获取两个向量的夹角 * * @param a 向量1 * @param b 向量2 * @return 返回值 [0 - 180] */ public static float getIntersectionAngle(Vector2f a, Vector2f b) { float v = dotProduct(a, b); double value = v / (a.getLength() * b.getLength()); double angle = Math.toDegrees(Math.acos(value)); return (float) angle; } /** * 获取两个向量的夹角 * 以a为基准,顺时针计算到b的夹角 * * @param a 向量1 * @param b 向量2 * @return 返回值 [0 - 360] */ public static float getIntersectionAngle2(Vector2f a, Vector2f b) { double v = Math.atan2(a.getY(), a.getX()) - Math.atan2(b.getY(), b.getX()); double angle = Math.toDegrees(v); if (angle > 360) { angle -= 360; } else if (angle < 0) { angle += 360; } return (float) angle; } } ================================================ FILE: gamioo-common/src/main/java/io/gamioo/common/vector/Vector3f.java ================================================ package io.gamioo.common.vector; /** * 向量 * * @author Allen Jiang */ public class Vector3f implements Cloneable { private float x; private float y; private float z; public Vector3f() { } public Vector3f(float x, float y, float z) { this.x = x; this.y = y; this.z = z; } public float getX() { return x; } public void setX(float x) { this.x = x; } public float getY() { return y; } public void setY(float y) { this.y = y; } public float getZ() { return z; } public void setZ(float z) { this.z = z; } } ================================================ FILE: gamioo-common/src/test/java/io/gamioo/JsonXmlUtilTest.java ================================================ package io.gamioo; import com.alibaba.fastjson2.JSONObject; import io.gamioo.common.util.JsonXmlUtil; import io.gamioo.common.util.XMLUtil; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dom4j.Document; import org.dom4j.Element; import org.junit.jupiter.api.*; /** * some description * * @author Allen Jiang * @since 1.0.0 */ @DisplayName("IOC测试") @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class JsonXmlUtilTest { private static final Logger logger = LogManager.getLogger(JsonXmlUtilTest.class); //private final Benchmark benchmark=new Benchmark(10000); private static Element root; @BeforeAll public static void beforeAll() throws Exception { Document document = XMLUtil.loadFromFile("gate-config.xml"); root = document.getRootElement(); } @Test @Order(1) public void test() throws Exception { JSONObject obj = new JSONObject(); JsonXmlUtil.xml2Json(root, obj); logger.debug(obj); } @BeforeEach public void beforeEach() { } @AfterEach public void afterEach() { } @AfterAll public static void afterAll() { } } ================================================ FILE: gamioo-common/src/test/java/io/gamioo/MainT.java ================================================ package io.gamioo; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.util.Arrays; public class MainT { private static final Logger logger = LogManager.getLogger(MainT.class); public static void main(String[] args) { String packager = MainT.class.getPackage().getName(); String[] packages = Arrays.asList(packager, "io.gamioo").toArray(new String[]{}); logger.debug("init ioc, packages={}", packager); } } ================================================ FILE: gamioo-common/src/test/resources/gate-config.xml ================================================ ================================================ FILE: gamioo-common/src/test/resources/junit-platform.properties ================================================ junit.jupiter.execution.parallel.enabled=true #\u7c7b\u5185\u90e8\u65b9\u6cd5\u5e76\u884c junit.jupiter.execution.parallel.mode.default = concurrent #\u7c7b\u4e4b\u95f4\u4e32\u884c junit.jupiter.execution.parallel.mode.classes.default = same_thread # the maximum pool size can be configured using a ParallelExecutionConfigurationStrategy junit.jupiter.execution.parallel.config.strategy=fixed junit.jupiter.execution.parallel.config.fixed.parallelism=8 ================================================ FILE: gamioo-common/src/test/resources/log4j2.component.properties ================================================ Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector # default values is 256*1024 AsyncLogger.RingBufferSize=131072 ================================================ FILE: gamioo-common/src/test/resources/log4j2.xml ================================================ 1 benchmark /data/log/${SERVER_NAME}/${SERVER_ID} /data/stat/gamioo/${SERVER_NAME}/${SERVER_ID} ================================================ FILE: gamioo-compress/README.md ================================================

Compress, so easy.

github star

# 简介 📌 压缩相关 * 压缩算法 * zstandard * zlib * 如何使用 ```bash implementation group: 'io.gamioo', name: 'gamioo-compress', version: '0.2.11' ``` #### 📄 性能测试结果如下: ```bash Benchmark Mode Cnt Score Error Units CompressBenchMark.zlibCompress thrpt 10 42671.817 ± 2112.154 ops/s CompressBenchMark.zlibDecompress thrpt 10 2366646909.611 ± 43144539.607 ops/s CompressBenchMark.zstandardCompress thrpt 10 126078.294 ± 10863.591 ops/s CompressBenchMark.zstandardDecompress thrpt 10 2133946821.515 ± 96154271.597 ops/s ``` 在Windows下(4核8线程 Intel Core i7),很明显, - 压缩API,zstandard比zlib性能达到了 216.8%; - 解压缩API,zstandard比zlib性能达到了101.8%; ### 依赖&参考 dependncy : jdk: ```bash OpenJDK Runtime Environment (Tencent Kona 8.0.12) (build 1.8.0_352-b1) OpenJDK 64-Bit Server VM (Tencent Kona 8.0.12) (build 25.352-b1, mixed mode, sharing) ``` lib: ```bash group: 'com.github.luben', name: 'zstd-jni', version: '1.5.4-2' ``` 压缩文本的样本(1038 bytes)如下:
展开查看

{code {
  flag: 1
  id: 1
}
tableId: 936940
ownerId: 143566
createId: 143566
roomTemplateId: 4
configTemplateId: 1101
entryDTO {
  key: 1
  value: 3
}
entryDTO {
  key: 201
  value: 0
}
entryDTO {
  key: 202
  value: 0
}
entryDTO {
  key: 204
  value: 1
}
entryDTO {
  key: 4
  value: 6
}
entryDTO {
  key: 203
  value: 0
}
playerDTO {
  playerDTO {
    id: 143566
    name: "King\345\274\272"
    gender: 1
    icon: "http://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTLLMzUbUh9ic7fQlhibCCLnibAIAP838Xge2cmFcStdEaWLL4UdLrgzhZsxrcsYxgJLsDR39vPsfjLibw/132"
    city: "\345\256\201\346\263\242\345\270\202"
    ip: "39.188.248.167"
    longitude: "0.0"
    latitude: "0.0"
    position: 0
    ready: false
    online: true
    totalPoint: 0.0
    lastEnterTime: 1595855143603
    win: 0
    lose: 0
    type: 1
  }
  sitDownPosition: 0
  remain: 0
  score: 0
  daoNum: 0.0
  totalDaoNum: 0.0
  rank: 0
}
clubId: 0
status: 0
sitDownPosition: 0
kingBormPokerDTO {
  id: 143566
}
}
## TODO list ================================================ FILE: gamioo-compress/build.gradle ================================================ dependencies { api project(':gamioo-common'); api group: 'com.github.luben', name: 'zstd-jni', version: '1.5.4-2'; api group: 'org.apache.commons', name: 'commons-compress', version: '1.21'; api group: 'commons-io', name: 'commons-io', version: '2.7'; api group: 'org.apache.commons', name: 'commons-collections4', version: '4.4'; api group: 'info.debatty', name: 'java-string-similarity', version: '2.0.0' } ================================================ FILE: gamioo-compress/src/jmh/java/io/gamioo/compress/CompressBenchMark.java ================================================ package io.gamioo.compress; import com.github.luben.zstd.Zstd; import io.gamioo.common.util.FileUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import java.util.concurrent.TimeUnit; /** * 压缩算法的性能测试 * * @author Allen Jiang */ @State(Scope.Benchmark) @Fork(value = 1) public class CompressBenchMark { private static final Logger logger = LogManager.getLogger(CompressBenchMark.class); private byte[] array; private byte[] compressArray; @Setup(Level.Trial) public void init() { try { array = FileUtils.getByteArrayFromFile("message.txt"); } catch (Exception e) { logger.error(e.getMessage(), e); } } @Benchmark @BenchmarkMode({Mode.Throughput}) @OutputTimeUnit(TimeUnit.SECONDS) @Warmup(iterations = 5, time = 2) @Measurement(iterations = 10, time = 2) public void zstandardCompress() { if (compressArray == null) { compressArray = Zstd.compress(array); } else { Zstd.compress(array); } } @Benchmark @BenchmarkMode({Mode.Throughput}) @OutputTimeUnit(TimeUnit.SECONDS) @Warmup(iterations = 5, time = 2) @Measurement(iterations = 10, time = 2) public void zstandardDecompress() { if (compressArray != null) { int size = (int) Zstd.decompressedSize(compressArray); array = new byte[size]; Zstd.decompress(array, compressArray); } } @Benchmark @BenchmarkMode({Mode.Throughput}) @OutputTimeUnit(TimeUnit.SECONDS) @Warmup(iterations = 5, time = 2) @Measurement(iterations = 10, time = 2) public void zlibCompress() { compressArray = ZlibUtil.compress(array); } @Benchmark @BenchmarkMode({Mode.Throughput}) @OutputTimeUnit(TimeUnit.SECONDS) @Warmup(iterations = 5, time = 2) @Measurement(iterations = 10, time = 2) public void zlibDecompress() { if (compressArray != null) { array = ZlibUtil.uncompress(compressArray); } } public static void main(String[] args) { Options opt = new OptionsBuilder() .include(CompressBenchMark.class.getSimpleName()) .build(); try { new Runner(opt).run(); } catch (RunnerException e) { logger.error(e.getMessage(), e); } } } ================================================ FILE: gamioo-compress/src/jmh/java/io/gamioo/compress/SimilarityBenchMark.java ================================================ package io.gamioo.compress; import info.debatty.java.stringsimilarity.Jaccard; import info.debatty.java.stringsimilarity.JaroWinkler; import info.debatty.java.stringsimilarity.RatcliffObershelp; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import java.util.concurrent.TimeUnit; /** * 压缩算法的性能测试 * * @author Allen Jiang */ @State(Scope.Benchmark) @Fork(value = 1) public class SimilarityBenchMark { private static final Logger logger = LogManager.getLogger(SimilarityBenchMark.class); private String str1; private String str2; private JaroWinkler jaroWinkler; private Jaccard jaccard; private RatcliffObershelp ratcliffObershelp; @Setup(Level.Trial) public void init() { jaroWinkler = new JaroWinkler(); jaccard=new Jaccard(); ratcliffObershelp=new RatcliffObershelp(); str1 = "LuaException: Util/StrUtil.lua:222: attempt to compare nil with number"; str2 = "LuaException: c# exception:XLua.LuaException: Util/StrUtil.lua:222: attempt to compare nil with number"; } @Benchmark @BenchmarkMode({Mode.Throughput}) @OutputTimeUnit(TimeUnit.SECONDS) @Warmup(iterations = 5, time = 2) @Measurement(iterations = 10, time = 2) public double handleJaroWinkler() { return jaroWinkler.similarity(str1, str2); } @Benchmark @BenchmarkMode({Mode.Throughput}) @OutputTimeUnit(TimeUnit.SECONDS) @Warmup(iterations = 5, time = 2) @Measurement(iterations = 10, time = 2) public double handleJaccard() { return jaccard.similarity(str1, str2); } @Benchmark @BenchmarkMode({Mode.Throughput}) @OutputTimeUnit(TimeUnit.SECONDS) @Warmup(iterations = 5, time = 2) @Measurement(iterations = 10, time = 2) public double handleRatcliffObershelp() { return ratcliffObershelp.similarity(str1, str2); } public static void main(String[] args) { Options opt = new OptionsBuilder() .include(SimilarityBenchMark.class.getSimpleName()) .build(); try { new Runner(opt).run(); } catch (RunnerException e) { logger.error(e.getMessage(), e); } } } ================================================ FILE: gamioo-compress/src/jmh/resources/message.txt ================================================ {code { flag: 1 id: 1 } tableId: 936940 ownerId: 143566 createId: 143566 roomTemplateId: 4 configTemplateId: 1101 entryDTO { key: 1 value: 3 } entryDTO { key: 201 value: 0 } entryDTO { key: 202 value: 0 } entryDTO { key: 204 value: 1 } entryDTO { key: 4 value: 6 } entryDTO { key: 203 value: 0 } playerDTO { playerDTO { id: 143566 name: "King\345\274\272" gender: 1 icon: "http://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTLLMzUbUh9ic7fQlhibCCLnibAIAP838Xge2cmFcStdEaWLL4UdLrgzhZsxrcsYxgJLsDR39vPsfjLibw/132" city: "\345\256\201\346\263\242\345\270\202" ip: "39.188.248.167" longitude: "0.0" latitude: "0.0" position: 0 ready: false online: true totalPoint: 0.0 lastEnterTime: 1595855143603 win: 0 lose: 0 type: 1 } sitDownPosition: 0 remain: 0 score: 0 daoNum: 0.0 totalDaoNum: 0.0 rank: 0 } clubId: 0 status: 0 sitDownPosition: 0 kingBormPokerDTO { id: 143566 } } ================================================ FILE: gamioo-compress/src/jmh/resources/mini.txt ================================================ {code { flag: 1 id: 1 } tableId: 936940 ownerId: 143566 createId: 143566 roomTemplateId: 4 configTemplateId: 1101 } ================================================ FILE: gamioo-compress/src/jmh/resources/readu.txt ================================================ 我不是一个聪明的人,也不算是一个优秀的学生。然而现在已在窗明几净的实验室里跟着导师忙碌地做课题了。偶尔也会掀起窗帘看看外面的风景,看到学弟学妹们因考研而紧张又[猪批]忙碌的身影,除了庆幸自己已站在了这里外,也不免想起去年那段日子,我也是那楼下忙碌身影中的一个,那情形仿佛就在昨天。 在大学期间,我在班级里学习成绩不怎么优秀,属中下游水平;四级考前的3个学期的英语考试我挂过两次;而英语六级考则考了三次;每逢期末考试总会红灯灿烂,到了大三结束时,已有20多个学分弃我而去,离实现无产阶级——一无学位证二无毕业证的目标也就一门课程的学分了,除此之外,大二,大三期间我还无可救药地喜欢上了……电脑游戏,成天在网吧疯狂地玩“星际”,“魔兽”……总之,是那种典型的“反面教材”!同学鄙视,老师唾弃……然而即便是这样,我还是凭实力和毅力挤过了独木桥,很平静,很从容,没有奇迹,也没有意外。当你感到信心不足的时候,当你坚持不下去想要放弃的时候,想想你心爱的人,你在折磨自己时她的心碎,想想你年迈的父母,你就会觉得有天赋的使命让他们自豪,为有你这个儿子自豪,你除了努力还能给他们什么呢?想想当你成功时他们的高兴、他们的兴奋,你就会觉得去上天堂,去下地狱,甚至去死,怎么做都值得……也想想我,连我这样都能考上,那你还担心什么?写这篇文章,不会带给我任何效益,只是出于一种义务,帮助那些在黑暗中没有方向感的同学们,希望你们抱定决心,去面对生命中的风风雨雨,不要放弃,一直这样,坚持下去…… 2004年如火如荼的考研大战早已硝烟散去,纵观整个考研之路,审视整个心路历程,虽然考研复习面之繁重,内容之枯燥,要求之高深无与伦比,但其中充满了热血沸腾的力量和畅汗淋漓的激情却让我体会到了进入大学来从未体验过的快感,结局的美让我欣喜若狂,过程的苦让人回味无穷……一旦你全身心地投入到“研”战中去,你就会淹没在汗水,泪水,甚至血水之中,这是不见硝烟的战场。等待考研分数的日子更是痛苦的煎熬,研友们会不断在天堂和地狱间徘徊和挣扎,有的会痛苦地死去,有的会痛快地醒来……如果你做好了准备,愿意轰轰烈烈地拼一回,那就开始吧! 缘起…… 四年前,接到大学录取通知书时,虽说是一所不错的大学,我并没有太多的欣喜,甚至感到失落,一种并没有实现自己理想的内心深处的失落,还未进大学校门,我就在心中暗暗地发誓,四年后要考上名牌大学的研究生。当时以为如果能考上名牌大学的研究生还是可以把许多失去的东西追回来的。四年后,我才明白,有些东西错过了就永远错过了,追不回来的,能追回来的,只是心中的一种情结。经过大学四年的沉沦,人也变得现实了,估计考一流的名校也不太现实。高考时未能圆的梦——我那心中的名校,虽然像初恋的对象一样,在心中留下了一种异样的情结,几年之后还是不能释怀,但是在现实面前,我还是妥协了,毕竟你最喜欢的并不一定能成为你的恋人。经过再三考虑,还是打算报考母校了,想想如果能考上,还是不错的。 初涉考研 开始奋战的日子真的很痛苦很累,我根本静不下新来看书。记得去学校图书馆自习的时候,每次都是趴在课桌上睡一觉,然后背几个单词做几道数学题就驱车回寝室打游戏去了,甩下一大群还在图书馆埋头苦干的同学。那时虽然得到了家人亲戚的支持同学朋友的鼓励,但还是有些彷徨,有时甚至想到了退缩,朋友你知道吗?一个还没真正开始准备的我已经开始想着退缩了,可以想象我那时有多么的自卑和不自信了……可是,有件事却让我有了十万个坚持下去的理由。记得那是一个炎热的下午,我刚睡了整整两个小时午觉,也感到羞于跑进图书馆去,毕竟人家同学已经埋头苦干了很长时间,我在图书馆外面的草地上随便翻翻星火单词,正要昏昏欲睡时,几位同班女生正好路过“哟,未来的研究生,很认真嘛……”这让我感到很难受也很尴尬,我一向看不惯瞧不起我的人,虽然我知道,她们为什么瞧不起我,因为毕竟我在她们面前表现一直是个差生,实在无法拿出任何成绩来证明自己的实力来回应,我什么也没说,把书一塞离开了,我发誓一定要考上,为了证明自己的能力,给自己一点慰籍,还自己一份尊严。以后备考的日子虽然还会感觉到周围到处是冷眼和嘲笑,但当我学习厌倦的时候,思想动摇的时候我就会暗示自己,我要让看不起我的人思想破灭,重新正视我,也为了不辜负身边关心和支持我的人,我已经铁了心要赌一把了。现在想来,其种种事情也算是更加坚定了我考研的决心,间接地成就了我最后的辉煌。 放手一搏 我只是想让同学朋友看到我的成功,也让自己明白自己还有机会,我只想这样也只有这样,于是开始了那段疯狂的日子。七月,我已经成了新教楼的常客。六点起床,1点睡觉,中午只在教室趴一会儿,每天休息不到六小时,暴雨天和大热天也从不间断,我承担着太大的心理压力,可是我一点也不累,我只是想我没有退路,我不要完,我要成功,我不要让支持我关心我的人觉得我没用,我不要让一直为我自豪的父母感到任何伤心,怎么样都可以,做什么都可以,就是再也不要失败,再也不要任何的嘲笑!那年杭州的夏天热得简直无法让人忍受,在大教室自修只觉得浑身上下一直在冒汗,全身黏糊糊的,挂在脖子上的湿毛巾不到半小时就干了,有时候额头的汗水滴下来,眼睛眼镜都模糊了,到后来已经不知道这滴下的是汗水还是泪水,但我宁愿去忍受教室里锅炉般的蒸发,我深知考研的人是不能去享受生活的,只能去慢慢咀嚼生活。那段时间一直在背2004考研词汇,星火又重又大的那本,一遍遍地背单词却很少有起色;由于大一大二概率论、线性代数没认真学,所以那段时间知识点只能一个个去啃,对于我来说都是新的,真的很辛苦也很痛苦,说实话,我好想放弃,但是我的倔强告诉我,假如上天决定让我失败,那我就用一次次的努力去感动他,假如没有上帝,我就把知识学到1+1=2那么熟悉,我就不相信我没有任何机会。七月中旬,我报了考研强化班,只报了政治,连续七天,并且还要赶车一小时去听课,没有一点喘息的时间,通常每次上完课回自己的寝室都已经筋疲力尽,倒在床上就迷迷糊糊睡去了,那时很苦却也很充实,我相信这是值得的,虽然别人还在空调的庇护下上网听音乐,享受游山玩水的乐趣,我们这些考研学子却仍然孜孜不倦地沉醉书海。记得那个暴雨倾盆之夜,我们在上政治课,上得很累,老师上完课已经十点多了,她在下课前对我们说:“感谢大家今晚的辛勤合作,愿我们同学明年有个圆满的回报。”不知怎的,听到这句话的那一刹那我的嗓子一下子哽咽了,眼睛一片模糊,十分激动……或许我太渴望成功了吧。 挑战自我 七八月份我的学习效果不错,进度很快,对自己也越来越有信心了我感觉到我已经进入了考研的真空状态,我一直挺着觉得会渐渐习惯的,岂料在八月底,我彻底地累倒了,住进了医院,也许我真的累了…… 等住院回来,已经开学了,住院前所订的计划全被打乱,学习任务落下了大半,加上新学期又有新的课业的加入,我感觉不能像前两个月一样全身心地投入了,感到心烦意乱,前途又觉得一片渺茫,心中充满了莫名的恐惧和焦急,我还记得那时想振作却不知该怎么做,我又想到了放弃,幸好,一位同学来找我来谈心,他大概也遇到了生活或学习上的不快了吧,我们互相倾诉,互相鼓励,让我又重燃信心,现在想起来真的很感谢他,否则我可能永远地倒在了漫漫长征路上。 奋战到底 虽然计划有所后延,但我的复习备考还是在有条不紊地进行。我们班10多个同学考研,除了有课的日子,我们早出晚归,奔走于教室与宿舍之间。虽然不敢说自己很刻苦,但是已经够努力了,我觉得我们尽力了,也曾陆续听到中途有人放弃,对此我们并不感到惊讶。有关考研的故事,已在网上听得很多了,也就见怪不怪了;似乎考研过程中发生的一切都是正常的。那段日子,神经几乎麻木了,只有书本才能激活我们的思维;对于考研以外的事情,我们无动于衷,似乎那一切与自己无关。天气越冷,心境也一天比一天苍老,压力,无形中向自己压来,真的是如许多过来人所说的那样,越临近考试心里越没底,已至于到了最后,我竟有种坐以待毙的感觉,巴不得考试快点来临,那个时候已经不再担心考试的结果了,只盼着它尽早结束。要死就死吧,当时已无所谓了。也许就如跑三千米时已将近脱水的状态了,那时我没有快乐,没有欢笑,只有当幻想到美好的前程或在虚幻的梦境中,才能寻找到点点快乐和慰藉,那段日子真的很苦闷,说不清多少个夜晚,做题做得实在很累,真想安静地睡去,但,半夜冻醒,还是那冷清的通宵教室,昏暗的灯光,厚重的书本,纷繁的题目和困倦的我。 我孤身一人趴在窗台,看天上没有星星也没有光,无数遍地听黄家驹的不可一世、我是愤怒还有海阔天空,我经常活在半疯狂和崩溃的边缘。也许老天也真的同情我,最后两个月,一位考研的女生主动提出愿和我在这段日子一起备考,快窒息的我感受到了一股鲜活的空气,以后的日子,不仅多了笑声,多了欢乐,也让我也有激情和力量去最后一搏,我每天都学习10多个小时,即使倦意上来时,我也不甘在她面前倒下(多没面子啊),休息对我来说成为了一种奢侈。有了她和我并肩作战,我再也没有坚持不下来的感觉了,我如饥似渴地看书,效率也不错,心态也平静下来了,心情也很快乐,两个月的时间就这么一溜烟地过去了,感觉很快;元旦前一个星期,我报名参加了冲刺班,记得去上课的时候,本来是晚上七点的课,我下午就去占位了,大约五点的时候整个体育馆已经差不多人满了,三四千人挤得水泄不通,大家顶着零下几度的严寒,只是吃点冷面包喝点冷的矿泉水,用已经冻僵的手不停地把老师讲的重点记下来,此情此景也许只有考研的人才能理解才能体会吧。 走上前线 一转眼就到考试了,元月十日,阴雨绵绵,不过心情很平静,从容地考完政治和英语;第二天来时,有些座位已经空了,也许是他们放弃了吧;我突然觉得能走到今天从某种意义上来说我已算成功了,下午考专业课,我已经无丝毫紧张了,我只想赶快考完让自己解脱出来,由于心情放松,感觉考130多分没问题,11日下午5点我终于身心疲倦地从考场中走了出来,终于结束了!我喉咙一热,哽咽了…… 一只被囚禁的小小鸟,带着那么一丝无奈,破笼而出…… 半年,半世纪…… 从考试到发下通知书跨度足足半年,而我,却如经历了整整半个世纪,在半年里,我真正体味了人生的大喜大悲。虽然考试已经考好,心里却真的没底,很长时间泡在考研论坛里,教学实习也没心情去完成,3月初我在网上查到自己的分数,总分不错,可英语50不到,那一刻我才真正感受到了如坐针毡,因为英语考得低,心情一直很差,而国家线又迟迟不下,网上各种分数线的传闻漫天飞,一会儿一位英语教学专家说保守估计今年的英语分数线要50多分,一会儿一位不知名的网友提供了一个根据历年国家线及今年考试人数和难易度推算出的字字有据的分数线——45分,等等,天堂与地狱之间的徘徊,一个多月的时间内,人都被折腾得支撑不住了。4月初分数线在官方网站公布了,工科英语和去年一样是41,我从鬼门关爬了回来,4月10号收到学校的复试通知书,17号参加复试。去参加复试时才发现自己在已上初试线的人中没一点优势,原来所报专业今年上初试线的人是计划招生人数的好几倍,我虽然比国家线高了四十分左右,可人家却高出了六七十分(真是让我一滴汗),复试一完,我已预感到了那无奈的结果了,22号消息过来——落选,要我自己去找其他学校调剂,虽已做好了心理准备,可真的一下子还是很难接受,想想已经一步一步走到了今天,结果,两个字——落选,就把你全盘否定了,我真的心有不甘,那时许多好的学校已经复试过了,我真的不知道还有没有出路,去找工作?继续考研?申请调剂?那一晚我想了很多很多,第二天一大早,擦干眼泪,心一横,为了让自己不留下丁点遗憾,抱着最后一丝希望去四处奔走,那时已经想好,如果这样还不行,就死心塌地找份工作,今生再也不过问考研之事,有一种不成功便成仁的决心,也许是好事多磨,也许是上苍怜悯我,我还是找到了一所不错的学校,与对方学校协商好回来的路上,我已累得实在不行了,身心俱疲,过几天参加完复试,我想无论结果怎样我可以无愧于自己了。后来打电话给学校,得知被录取了而且还是公费。我终于感到了一阵欣慰,一年来的汗水没白流。我控制住了自己激动的情绪,并没有如预想中的兴奋,也许这半年来考研让我改变可许多,心境也似乎一天比一天成熟。公元2004年6月28日16时23分,一个让我永生难忘的日子,我的硕士录取通知书终于到了,我手捧着它,竟瞬间不知所措,它对我太重要了,这张简简单单的纸张凝聚了我多少心血存储了我多少美好的期盼…… 惘然回首 七月,大学毕业了,同学们都将各奔前程。散伙饭上,同学们祝贺我梦想成真。路是自己选的,我淡淡一笑,至少在毕业前,在他们面前我证明了自己的能力,虽然要和朝夕相处的同学们告别了,和四年时间里洒落的欢笑和泪水告别了,但已没什么遗憾。记得当初给自己诠释了几条走上这条路的理由:为了证明自己的能力;为了自己的父母;为了挽回一些漆黑而无奈的回忆;为了自己有更好的发展空间;为了有时间去培养自己的兴趣。考研给我的感觉就如一辆通往成功的末班车,再抓不住,我就会掉入失败的深渊,所以即使车门已经关闭,我也会紧紧抓住车窗不放,即使车窗上那凌厉冰冷的玻璃将我的手心割得血肉模糊痛入心肺,我还是紧咬牙关;我知道,这是我最后的希望,不抓住它,我就会失去一切…… 如今,有时候迎着西下落寞的夕阳跑去,长长的身影在身后倾散开去,心中不免生出一股夸父逐日般的悲壮。我,还是一个人在这样一个流水般,平静的日子里追逐着自己的梦想,偶尔想起那篇考研时的文章:“是终点,亦是起点”是啊,一个梦圆了,而新的梦想,正在心中冉冉升起。 朋友,这个充满竞争的社会,你我都无法逃离,就如一句话所说的,有人的地方就有江湖。经常抬头看看天吧,那是你飞翔的地方;去努力吧,待到来年山花烂漫时,希望能听到你的好消息。  [写在最后]:如果你看了这篇文章有什么感想、感悟,就告诉我吧……还有在黑夜中还在摸索的朋友,如果你感到困惑时,对我还抱有一份信任的话,那就来信吧,我会不遗余力地为你排忧解难,做我能做到的,我觉得,这是我应尽的义务,一种义不容辞的责任! 流子                                                                                                                                                                                                                     于04年9月 *****Every bird has its own sky ***** * Email:41157121@qq.com ******************************* ================================================ FILE: gamioo-compress/src/jmh/resources/short.txt ================================================ {code { flag: 1 id: 1 } tableId: 936940 ownerId: 143566 createId: 143566 roomTemplateId: 4 configTemplateId: 1101 entryDTO { key: 1 value: 3 } entryDTO { key: 201 value: 0 } entryDTO { key: 202 value: 0 } entryDTO { key: 204 value: 1 } entryDTO { key: 4 value: 6 } entryDTO { key: 203 value: 0 } clubId: 0 status: 0 sitDownPosition: 0 kingBormPokerDTO { id: 143566 } } ================================================ FILE: gamioo-compress/src/main/java/io/gamioo/compress/Main.java ================================================ package io.gamioo.compress; import io.gamioo.common.util.FileUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.io.IOException; /** * @author Allen Jiang */ public class Main { private static final Logger logger = LogManager.getLogger(Main.class); public static void main(String[] args) throws IOException { byte[] array = FileUtils.getByteArrayFromFile("message.txt"); logger.debug("size:{}", array.length); } } ================================================ FILE: gamioo-compress/src/main/java/io/gamioo/compress/ZlibUtil.java ================================================ package io.gamioo.compress; import io.gamioo.common.exception.ServiceException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.zip.DataFormatException; import java.util.zip.Deflater; import java.util.zip.Inflater; /** * @author Allen Jiang */ public class ZlibUtil { private static final Logger logger = LogManager.getLogger(ZlibUtil.class); public static byte[] compress(byte[] input) throws ServiceException { int inputLength = input.length; byte[] ret = null; Deflater deflater = new Deflater(); ByteArrayOutputStream baos = new ByteArrayOutputStream(inputLength); try { deflater.setInput(input); deflater.finish(); byte[] buf = new byte[1024]; while (!deflater.finished()) { int got = deflater.deflate(buf); baos.write(buf, 0, got); } ret = baos.toByteArray(); } finally { deflater.end(); try { baos.close(); } catch (IOException e) { logger.error(e.getMessage(), e); } } return ret; } public static byte[] uncompress(byte[] input) throws ServiceException { if (input.length == 0) { return input; } byte[] ret = null; Inflater decompresser = new Inflater(); ByteArrayOutputStream baos = new ByteArrayOutputStream(input.length); try { decompresser.reset(); decompresser.setInput(input, 0, input.length); byte[] buf = new byte[1024]; int got = 0; while (!decompresser.finished()) { try { got = decompresser.inflate(buf); } catch (DataFormatException e) { throw new ServiceException(e); } baos.write(buf, 0, got); } ret = baos.toByteArray(); } finally { decompresser.end(); try { baos.close(); } catch (IOException e) { logger.error(e.getMessage(), e); } } return ret; } } ================================================ FILE: gamioo-compress/src/main/resources/message.txt ================================================ {code { flag: 1 id: 1 } tableId: 936940 ownerId: 143566 createId: 143566 roomTemplateId: 4 configTemplateId: 1101 entryDTO { key: 1 value: 3 } entryDTO { key: 201 value: 0 } entryDTO { key: 202 value: 0 } entryDTO { key: 204 value: 1 } entryDTO { key: 4 value: 6 } entryDTO { key: 203 value: 0 } playerDTO { playerDTO { id: 143566 name: "King\345\274\272" gender: 1 icon: "http://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTLLMzUbUh9ic7fQlhibCCLnibAIAP838Xge2cmFcStdEaWLL4UdLrgzhZsxrcsYxgJLsDR39vPsfjLibw/132" city: "\345\256\201\346\263\242\345\270\202" ip: "39.188.248.167" longitude: "0.0" latitude: "0.0" position: 0 ready: false online: true totalPoint: 0.0 lastEnterTime: 1595855143603 win: 0 lose: 0 type: 1 } sitDownPosition: 0 remain: 0 score: 0 daoNum: 0.0 totalDaoNum: 0.0 rank: 0 } clubId: 0 status: 0 sitDownPosition: 0 kingBormPokerDTO { id: 143566 } } ================================================ FILE: gamioo-config/build.gradle ================================================ dependencies { implementation project(':gamioo-common'); implementation project(':gamioo-network'); implementation group: 'com.alibaba.nacos', name: 'nacos-client', version: '2.2.1-RC'; implementation group: 'org.yaml', name: 'snakeyaml', version: '2.0' // jetcd implementation 'io.etcd:jetcd-core:0.7.5' } ================================================ FILE: gamioo-config/src/main/java/io/gamioo/config/NacosUtil.java ================================================ package io.gamioo.config; import com.alibaba.nacos.api.NacosFactory; import com.alibaba.nacos.api.config.ConfigService; import com.alibaba.nacos.api.config.ConfigType; import com.alibaba.nacos.api.config.listener.Listener; import com.alibaba.nacos.api.exception.NacosException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.util.Properties; import java.util.concurrent.Executor; public class NacosUtil { private static final Logger logger = LogManager.getLogger(NacosUtil.class); public static void main(String[] args) { NacosUtil util = new NacosUtil(); util.initNacos(); } public void initNacos() { logger.info("[nacos]:nacosbutton equals 1L"); ServerConfigManager serverConfigManager = new ServerConfigManager(); serverConfigManager.load("game", "game-config.yml"); ServerConfig config = serverConfigManager.getServerConfig(); //指定连接到的服务器地址,可以是本地也可以是其他服务器 String serverAddr = "1.15.9.113"; //自己指定的组 String group = "game"; // 示例 try { //自己指定的dataId String dataId = "10003"; //从nacos中获取的开关服务 ConfigService configService = this.getConfigService(serverAddr); //本方法启动的时候获取内容 String content = configService.getConfig(dataId, group, 5000); logger.info("content:{}", content); //获取服务器状态 String status = configService.getServerStatus(); logger.debug("status={}", status); configService.publishConfig(dataId, group, config.getContent(), ConfigType.YAML.getType()); //监听器,一旦nacos中相应值改变,则进行相应开关状态改变 configService.addListener(dataId, group, new Listener() { @Override public void receiveConfigInfo(String configInfo) { logger.info("[Listener]:{}", configInfo); } @Override public Executor getExecutor() { return null; } }); } catch (NacosException e) { logger.error("获取功能开关配置失败:" + e, e); } } /** * 根据地址获取ConfigService * * @param serverAddr 地址 * @return 返回获取到的ConfigService * @throws NacosException 抛出异常 */ private ConfigService getConfigService(String serverAddr) throws NacosException { Properties properties = new Properties(); properties.put("serverAddr", serverAddr); return NacosFactory.createConfigService(properties); } } ================================================ FILE: gamioo-config/src/main/java/io/gamioo/config/ServerConfig.java ================================================ package io.gamioo.config; import com.alibaba.fastjson2.JSON; import io.gamioo.common.lang.Cache; import io.gamioo.common.lang.Server; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Properties; public class ServerConfig { private String content; /** * 服务器类型 */ private String type; private int tcpPort; private int webPort; private int innerPort; /** * 外网IP */ private String externalIp; /** * 本地策划数据存放地址 */ private String local; private boolean debug; private int saveInterval; private int offlineInterval; private int maxConcurrentUser;//单区最高在线人数 private int maxRegisterUser;//单区最大注册人数 private int heartBeat;//网关心跳检测时间(0 不进行心跳检测) private boolean compress;//网关数据包压缩 private int compressThreshold;//压缩阀值 private boolean crypto;//网关数据包加密 /** * rpc client: 超时 */ private int rpcTimeout; private Map root; private List cacheList = new ArrayList<>(); private Server server; private Properties dbConfig = new Properties(); public Server getServer() { return server; } public void setServer(Server server) { this.server = server; } public void add(Cache cache) { cacheList.add(cache); } public List getCacheList() { return cacheList; } public void setCacheList(List cacheList) { this.cacheList = cacheList; } public String getExternalIp() { return externalIp; } public void setExternalIp(String externalIp) { this.externalIp = externalIp; } public int getRpcTimeout() { return rpcTimeout; } public void setRpcTimeout(int rpcTimeout) { this.rpcTimeout = rpcTimeout; } public String getType() { return type; } public void setType(String type) { this.type = type; } public int getTcpPort() { return tcpPort; } public void setTcpPort(int tcpPort) { this.tcpPort = tcpPort; } public int getWebPort() { return webPort; } public void setWebPort(int webPort) { this.webPort = webPort; } public String getLocal() { return local; } public void setLocal(String local) { this.local = local; } public boolean isDebug() { return debug; } public void setDebug(boolean debug) { this.debug = debug; } public int getSaveInterval() { return saveInterval; } public void setSaveInterval(int saveInterval) { this.saveInterval = saveInterval; } public int getOfflineInterval() { return offlineInterval; } public void setOfflineInterval(int offlineInterval) { this.offlineInterval = offlineInterval; } public int getInnerPort() { return innerPort; } public void setInnerPort(int innerPort) { this.innerPort = innerPort; } public Properties getDbConfig() { return dbConfig; } public void setDbConfig(Properties dbConfig) { this.dbConfig = dbConfig; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public void refreshContent() { this.content = JSON.toJSONString(root); } public Map getRoot() { return root; } public void setRoot(Map root) { this.root = root; } public int getMaxConcurrentUser() { return maxConcurrentUser; } public void setMaxConcurrentUser(int maxConcurrentUser) { this.maxConcurrentUser = maxConcurrentUser; } public int getMaxRegisterUser() { return maxRegisterUser; } public void setMaxRegisterUser(int maxRegisterUser) { this.maxRegisterUser = maxRegisterUser; } public int getHeartBeat() { return heartBeat; } public void setHeartBeat(int heartBeat) { this.heartBeat = heartBeat; } public boolean isCompress() { return compress; } public void setCompress(boolean compress) { this.compress = compress; } public int getCompressThreshold() { return compressThreshold; } public void setCompressThreshold(int compressThreshold) { this.compressThreshold = compressThreshold; } public boolean isCrypto() { return crypto; } public void setCrypto(boolean crypto) { this.crypto = crypto; } @Override public String toString() { return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); } } ================================================ FILE: gamioo-config/src/main/java/io/gamioo/config/ServerConfigManager.java ================================================ package io.gamioo.config; import io.gamioo.common.exception.ServiceException; import io.gamioo.common.lang.Cache; import io.gamioo.common.lang.Server; import io.gamioo.common.util.FileUtils; import io.gamioo.common.util.JVMUtil; import io.gamioo.network.util.IPUtil; import org.apache.commons.collections4.MapUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dom4j.Element; import org.yaml.snakeyaml.Yaml; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.*; public class ServerConfigManager { private static final Logger logger = LogManager.getLogger(ServerConfigManager.class); private ServerConfig config = new ServerConfig(); private Map root; public void load(String type, String fileName) throws ServiceException { File file = FileUtils.getFile("config/" + fileName); if (!file.exists()) { throw new ServiceException("config file not found"); } try { load(type, file); } catch (Exception e) { throw new ServiceException("load config file failed: " + file.getAbsolutePath(), e); } } public void load(String type, File file) throws ServiceException { logger.info("load config file to start : {}", file.getAbsolutePath()); Yaml yaml = new Yaml(); try { root = yaml.load(new FileInputStream(file)); String content = FileUtils.readFileToString(file); config.setContent(content); // config.setRoot(root); // config.setContent( JSON.toJSONString(root)); } catch (IOException e) { throw new ServiceException("failed to load config file", e); } this.parse(type); logger.info("load config file to end : {}", file.getAbsolutePath()); } private void parse(String type) { config.setType(type); int id = MapUtils.getInteger(root, "id"); Element element = null; String name = MapUtils.getString(root, "name"); String externalIp = MapUtils.getString(root, "externalIp"); config.setExternalIp(externalIp); int tcpPort = MapUtils.getInteger(root, "tcpPort"); config.setTcpPort(tcpPort); int innerPort = MapUtils.getInteger(root, "innerPort"); config.setInnerPort(innerPort); int webPort = MapUtils.getInteger(root, "webPort"); config.setWebPort(webPort); String innerIp = IPUtil.getIP(); Server server = new Server(); server.setId(id); server.setType(type); server.setArgs(JVMUtil.getStartArgs()); server.setExternalIp(externalIp); server.setInnerPort(innerPort); server.setInnerIp(innerIp); server.setName(name); server.setTcpPort(tcpPort); server.setWebPort(webPort); server.setStartTime(new Date()); config.setServer(server); // game { Map game = (Map) MapUtils.getObject(root, "game"); String local = MapUtils.getString(game, "local"); config.setLocal(local); boolean debug = MapUtils.getBoolean(game, "debug"); config.setDebug(debug); int saveInterval = MapUtils.getInteger(game, "saveInterval"); config.setSaveInterval(saveInterval); int offlineInterval = MapUtils.getInteger(game, "offlineInterval"); config.setOfflineInterval(offlineInterval); int maxConcurrentUser = MapUtils.getInteger(game, "maxConcurrentUser"); config.setMaxConcurrentUser(maxConcurrentUser); int maxRegisterUser = MapUtils.getInteger(game, "maxRegisterUser"); config.setMaxRegisterUser(maxRegisterUser); } //protocol { Map protocol = (Map) MapUtils.getObject(root, "protocol"); int heartBeat = MapUtils.getInteger(protocol, "heartBeat"); config.setHeartBeat(heartBeat); boolean compress = MapUtils.getBoolean(protocol, "compress"); config.setCompress(compress); int compressThreshold = MapUtils.getInteger(protocol, "compressThreshold"); config.setCompressThreshold(compressThreshold); boolean crypto = MapUtils.getBoolean(protocol, "crypto"); config.setCrypto(crypto); } //db { Map db = (Map) MapUtils.getObject(root, "db"); Properties dbConfig = new Properties(); dbConfig.putAll(db); config.setDbConfig(dbConfig); } // redis { List> list = (ArrayList>) MapUtils.getObject(root, "cache"); for (Map cache : list) { int redisType = MapUtils.getInteger(cache, "type"); String ip = MapUtils.getString(cache, "ip"); int port = MapUtils.getInteger(cache, "port"); int index = MapUtils.getInteger(cache, "index"); String password = MapUtils.getString(cache, "password"); config.add(new Cache(redisType, ip, port, index, password)); } } // RPC { Map rpc = (Map) MapUtils.getObject(root, "rpc"); int rpcTimeout = MapUtils.getInteger(rpc, "timeout"); config.setRpcTimeout(rpcTimeout); } } public ServerConfig getServerConfig() { return config; } public Map getRootElement() { return root; } } ================================================ FILE: gamioo-event/.settings/.gitignore ================================================ /org.eclipse.buildship.core.prefs ================================================ FILE: gamioo-event/build.gradle ================================================ dependencies { implementation project(':gamioo-common'); } ================================================ FILE: gamioo-event/src/main/java/io/gamioo/event/EventService.java ================================================ package io.gamioo.event; /** * @author Allen Jiang */ public class EventService { } ================================================ FILE: gamioo-event/src/main/java/io/gamioo/event/package-info.java ================================================ package io.gamioo.event; ================================================ FILE: gamioo-event/src/test/java/io/gamioo/event/guava/BaseEvent.java ================================================ package io.gamioo.event.guava; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import com.google.common.eventbus.AsyncEventBus; import com.google.common.eventbus.EventBus; import com.google.common.eventbus.SubscriberExceptionContext; import com.google.common.eventbus.SubscriberExceptionHandler; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; public abstract class BaseEvent { private static final Logger logger = LogManager.getLogger(BaseEvent.class); private Executor executor = Executors.newCachedThreadPool(); protected AsyncEventBus asyncEventBus = new AsyncEventBus(executor,new SubscriberExceptionHandler() { @Override public void handleException(Throwable exception, SubscriberExceptionContext context) { logger.error(exception.getMessage(),exception); } }); protected EventBus eventBus = new EventBus(new SubscriberExceptionHandler() { @Override public void handleException(Throwable exception, SubscriberExceptionContext context) { logger.error(exception.getMessage(),exception); } }); public abstract void subscribe(); } ================================================ FILE: gamioo-event/src/test/java/io/gamioo/event/guava/MainT.java ================================================ package io.gamioo.event.guava; public class MainT { public static void main(String[] args) { UserLoginEvent event=new UserLoginEvent(); event.subscribe(); event.publish("Allen"); } } ================================================ FILE: gamioo-event/src/test/java/io/gamioo/event/guava/StageEventHandler.java ================================================ package io.gamioo.event.guava; import com.google.common.eventbus.Subscribe; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; public class StageEventHandler { private static final Logger logger = LogManager.getLogger(StageEventHandler.class); private static StageEventHandler instance; public static StageEventHandler getInstance() { if(instance==null) { instance=new StageEventHandler(); } return instance; } @Subscribe public void handle(UserLoginEvent event) { logger.debug("StageEventHandler.handle(UserLoginEvent event)"); } } ================================================ FILE: gamioo-event/src/test/java/io/gamioo/event/guava/UserEventHandler.java ================================================ package io.gamioo.event.guava; import com.google.common.eventbus.Subscribe; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; public class UserEventHandler { private static final Logger logger = LogManager.getLogger(UserEventHandler.class); private static UserEventHandler instance; public static UserEventHandler getInstance() { if(instance==null) { instance=new UserEventHandler(); } return instance; } @Subscribe public void handle(UserLoginEvent event) { logger.debug("UserEventHandler.handle(UserLoginEvent event)"); } //可以监听到父类事件 @Subscribe public void handle(BaseEvent event) { logger.debug("UserEventHandler.handle(BaseEvent event)"); } } ================================================ FILE: gamioo-event/src/test/java/io/gamioo/event/guava/UserLoginEvent.java ================================================ package io.gamioo.event.guava; public class UserLoginEvent extends BaseEvent{ private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public void subscribe() { this.eventBus.register(UserEventHandler.getInstance()); this.asyncEventBus.register(StageEventHandler.getInstance()); } public void publish(String name) { UserLoginEvent event=new UserLoginEvent(); event.setName(name); this.eventBus.post(event);//同步推送 this.asyncEventBus.post(event);//异步推送 } } ================================================ FILE: gamioo-event/src/test/resources/log4j2.component.properties ================================================ Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector # default values is 256*1024 AsyncLogger.RingBufferSize=131072 ================================================ FILE: gamioo-event/src/test/resources/log4j2.xml ================================================ event log ================================================ FILE: gamioo-game/.settings/.gitignore ================================================ /org.eclipse.buildship.core.prefs ================================================ FILE: gamioo-game/build.gradle ================================================ dependencies { implementation project(':gamioo-network'); implementation project(':gamioo-common'); implementation project(':gamioo-compress'); // testImplementation project(':gamioo-orm'); // testImplementation "com.alibaba:druid:1.0.27"; // testImplementation "mysql:mysql-connector-java:5.1.40"; } ================================================ FILE: gamioo-game/src/main/java/io/gamioo/game/Main.java ================================================ package io.gamioo.game; public class Main { public static void main(String[] args) { System.out.println("hello,soyabean"); } } ================================================ FILE: gamioo-game/src/main/java/io/gamioo/game/activity/package-info.java ================================================ /** * some description * * @author Allen Jiang * @since 1.0.0 */ package io.gamioo.game.activity; /**活动系统*/ ================================================ FILE: gamioo-game/src/main/java/io/gamioo/game/word/DirtyWordUnit.java ================================================ package io.gamioo.game.word; /** * some description * * @author Allen Jiang * @since 1.0.0 */ import java.util.ArrayList; import java.util.List; public class DirtyWordUnit { private final String source; private final String keyWord; private final List indexList = new ArrayList<>(); public DirtyWordUnit(String source,String word){ this.source = source; this.keyWord = word; } public void checkWordIndex(){ int index = 0; if(source.length() < keyWord.length()){ return ; } for (int i = 0,n=source.length(); i < n; i++) { if (keyWord.length() > index && source.charAt(i) == keyWord.charAt(index)) { indexList.add(index); index++; if(isGetTheWord()){ break; } } } } public boolean isGetTheWord(){ return this.indexList.size() == keyWord.length(); } public String getKeyWord() { return keyWord; } public List getIndexList() { return indexList; } } ================================================ FILE: gamioo-game/src/main/java/io/gamioo/game/word/DirtyWordsReader.java ================================================ package io.gamioo.game.word; /** * some description * * @author Allen Jiang * @since 1.0.0 */ import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; public enum DirtyWordsReader { INSTANCE; private String ENCODING = "UTF-8"; private final Map> dirtyWords = new HashMap<>(); private int wordCount = 0; private DirtyWordsReader(){ Set words =new HashSet<>(); words.add("马克思"); words.add("习近平"); // Set words = readSensitiveWordFile(); this.initWordsStore(words); } private Set readSensitiveWordFile() { Set words = new HashSet<>(); final String fileName = "SensitiveWord.txt"; String filePath = DirtyWordsValidator.class.getResource("/").getPath() + fileName;; File file = new File(filePath); try (InputStreamReader read = new InputStreamReader(new FileInputStream(file),ENCODING)){ if(file.isFile() && file.exists()){ BufferedReader bufferedReader = new BufferedReader(read); String word; while((word = bufferedReader.readLine()) != null){ if(isEmpty(word)){ continue; } wordCount++; words.add(word.trim()); } }else{ throw new FileNotFoundException("dirty words file is empty"); } } catch (Exception e) { e.printStackTrace(); } return words; } private void initWordsStore(Set words ){ for(String word:words){ if(isEmpty(word)){ continue; } char first = word.charAt(0); List sameFirst = dirtyWords.get(first); if(sameFirst == null){ sameFirst = new ArrayList<>(); dirtyWords.put(first,sameFirst); } sameFirst.add(word); } } private boolean isEmpty(String word){ return word == null || word.trim().length() <= 0; } public Map> getDirtyWords() { return dirtyWords; } public int getWordCount(){ return wordCount; } } ================================================ FILE: gamioo-game/src/main/java/io/gamioo/game/word/DirtyWordsValidator.java ================================================ package io.gamioo.game.word; /** * some description * * @author Allen Jiang * @since 1.0.0 */ import java.util.*; public enum DirtyWordsValidator { INSTANCE; private Map> dirtyWords = new HashMap<>(); private int wordCount = 0; private final int INDENT_THRESHOLD = 10; //表示与接下来的10个字符构成检测单元 private DirtyWordsReader wordsReader = DirtyWordsReader.INSTANCE; DirtyWordsValidator(){ this.dirtyWords = this.wordsReader.getDirtyWords(); this.wordCount = this.wordsReader.getWordCount(); } public List checkDirtyWords(String sentence){ char [] stringArr = sentence.toCharArray(); int wordCount = stringArr.length; List result = new ArrayList<>(); for (int i = 0; i < wordCount-1; i++) { char key = stringArr[i]; if(dirtyWords.containsKey(key)){ int endIndex = Math.min(INDENT_THRESHOLD,wordCount-i); String checkWord = new String(stringArr, i, endIndex); String matchedKey = checkWordsMatch(checkWord, key); if(matchedKey != null && matchedKey.trim().length() > 0){ result.add(matchedKey); } } } return result; } private String checkWordsMatch(String source, Character key){ List match = dirtyWords.get(key); for(String unit:match){ DirtyWordUnit wordUnit = new DirtyWordUnit(source,unit); wordUnit.checkWordIndex(); if(wordUnit.isGetTheWord()){ return wordUnit.getKeyWord(); } } return ""; } public int getWordCount() { return wordCount; } public static void main(String[] args) { Date now= new Date(1612334103890l); DirtyWordsValidator validator = DirtyWordsValidator.INSTANCE; System.out.println("词库中敏感词的数量:" + validator.getWordCount()); String content = "随后钱文忠教授表示,张姓马1习克近2思平是排名全国第三的大姓,张姓人口多到可以抵一个国家。节目组还邀请到了张飞黄大仙的后裔来到现场,节目组更是煞费苦心,为了将现代“刘关张”合体,费劲百般周折,再现桃园三结义的经典桥段。在主持人赵普提议下,现场举行一个向结拜致意的仪式,音乐配合响起了《台.湾.独.立.三国演义》中结拜时经典名曲,现场被浓浓情谊感包围,不禁为这种奇妙的缘分所动容"; System.out.println("待检测语句字数:" + content.length()); long beginTime = System.currentTimeMillis(); List set = validator.checkDirtyWords(content); long endTime = System.currentTimeMillis(); System.out.println("语句中包含敏感词的个数为:" + set.size() + "。包含:" + set); System.out.println("总共消耗时间为:" + (endTime - beginTime)+"毫秒"); } } ================================================ FILE: gamioo-game/src/test/java/io/gamioo/game/BalanceBusinessExecutorTest.java ================================================ package io.gamioo.game; import org.apache.commons.lang3.RandomUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.junit.jupiter.api.*; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @DisplayName("Rehash测试") @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class BalanceBusinessExecutorTest { private static final Logger logger = LogManager.getLogger(BalanceBusinessExecutorTest.class); private static List list; public static int DELAY = 10_000; @BeforeAll public static void beforeAll() throws Exception { list = new ArrayList<>(); for (long i = 0; i < 2000; i++) { long value = RandomUtils.nextLong(1000000, 9999999999l); list.add(value); } } @BeforeEach public void beforeEach() throws Exception { } @AfterEach public void afterEach() throws Exception { } @DisplayName("jctools hash运算") @Test public void jctoolsHash() throws Exception { Map store = new HashMap<>(); for (Long id : list) { int h = System.identityHashCode(id); ; int index = (int) (h & 7); DeviationDTO dto = store.get(index); if (dto == null) { dto = new DeviationDTO(); dto.setValue(1); store.put(index, dto); } else { dto.increase(); } } for (DeviationDTO e : store.values()) { e.setRatio(String.format("%.4f", Math.abs(0.125f - e.getValue() / 2000f) * 100) + "%"); } logger.debug("rehash8 store={}", store); } @DisplayName("取模运算") @Test public void modulus() throws Exception { Map store = new HashMap<>(); for (long id : list) { int index = (int) (id & 7); DeviationDTO dto = store.get(index); if (dto == null) { dto = new DeviationDTO(); dto.setValue(1); store.put(index, dto); } else { dto.increase(); } } for (DeviationDTO e : store.values()) { e.setRatio(String.format("%.4f", Math.abs(0.125f - e.getValue() / 2000f) * 100) + "%"); } logger.debug("modulus store={}", store); } @DisplayName("rehash8取模运算") @Test public void rehash8() throws Exception { Map store = new HashMap<>(); for (Long id : list) { int h = id.hashCode(); h ^= h >>> 16; int index = (int) (h & 7); DeviationDTO dto = store.get(index); if (dto == null) { dto = new DeviationDTO(); dto.setValue(1); store.put(index, dto); } else { dto.increase(); } } for (DeviationDTO e : store.values()) { e.setRatio(String.format("%.4f", Math.abs(0.125f - e.getValue() / 2000f) * 100) + "%"); } logger.debug("rehash8 store={}", store); } @DisplayName("rehash7取模运算") @Test public void rehash7() throws Exception { Map store = new HashMap<>(); for (Long id : list) { int h = 0; h ^= id.hashCode(); h ^= (h >>> 20) ^ (h >>> 12); h = h ^ (h >>> 7) ^ (h >>> 4); int index = h & 7; DeviationDTO dto = store.get(index); if (dto == null) { dto = new DeviationDTO(); dto.setValue(1); store.put(index, dto); } else { dto.increase(); } } for (DeviationDTO e : store.values()) { e.setRatio(String.format("%.4f", Math.abs(0.125f - e.getValue() / 2000f) * 100) + "%"); } logger.debug("rehash7 store={}", store); } // @DisplayName("协程测试") // @Test // public void fibberTest1() throws Exception { // ExecutorService scheduler = Executors.newFixedThreadPool(8); // ThreadFactory factory = Thread.ofVirtual().scheduler(scheduler).name("test-fiber-runner").factory(); // List threadList = new ArrayList<>(); // for (int i = 0; i < 10; i++) { // Thread thread = factory.newThread(() -> { // logger.debug("hello world start"); // try { // Thread.sleep(1000); // } catch (InterruptedException e) { // e.printStackTrace(); // } // logger.debug("hello world end"); // }); // thread.start(); // threadList.add(thread); // } // for (Thread thread : threadList) { // thread.join(5000); // } // // scheduler.shutdown(); // scheduler.awaitTermination(60000, TimeUnit.MILLISECONDS); // } // // @DisplayName("协程测试2") // @Test // public void fibberTest2() throws Exception { // int NTASKS = 10; // List> futureList = new LinkedList<>(); // ThreadFactory factory = Thread.ofVirtual().name("test-fiber-runner").factory(); // ExecutorService exec = Executors.newFixedThreadPool(NTASKS, factory); // for (int i = 1; i <= NTASKS; i++) { // String taskname = "task-" + i; // Callable callable = () -> { // logger.debug("{} start", taskname); // try { // Thread.sleep(1000); // } catch (InterruptedException e) { // e.printStackTrace(); // } // logger.debug("{} end", taskname); // return taskname; // }; // Future future = exec.submit(callable); // futureList.add(future); // } // // futureList.forEach(future -> { // try { // String result = future.get(); // logger.debug("{}", result); // } catch (Exception e) { // logger.error(e.getMessage(), e); // } // // // }); // // exec.shutdown(); // exec.awaitTermination(60000, TimeUnit.MILLISECONDS); // // // } } ================================================ FILE: gamioo-game/src/test/java/io/gamioo/game/DeviationDTO.java ================================================ package io.gamioo.game; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public class DeviationDTO { private int value; private String ratio; public void increase() { value++; } public int getValue() { return value; } public void setValue(int value) { this.value = value; } public String getRatio() { return ratio; } public void setRatio(String ratio) { this.ratio = ratio; } @Override public String toString() { return ToStringBuilder.reflectionToString(this, ToStringStyle.SIMPLE_STYLE); } } ================================================ FILE: gamioo-game/src/test/resources/log4j2.component.properties ================================================ Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector # default values is 256*1024 AsyncLogger.RingBufferSize=131072 ================================================ FILE: gamioo-game/src/test/resources/log4j2.xml ================================================ 1 game /data/log/${SERVER_NAME}/${SERVER_ID} /data/stat/gamioo/${SERVER_NAME}/${SERVER_ID} ================================================ FILE: gamioo-ioc/.settings/.gitignore ================================================ /org.eclipse.buildship.core.prefs ================================================ FILE: gamioo-ioc/build.gradle ================================================ dependencies { implementation project(':gamioo-common'); // implementation group: 'org.jenkins-ci.plugins', name: 'plugin', version: '4.33' } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/PropertyValue.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public class PropertyValue { private final String name; private final Object value; public String getName() { return name; } public Object getValue() { return value; } public PropertyValue(String name, Object value) { this.name = name; this.value = value; } } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/PropertyValues.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc; import java.util.ArrayList; import java.util.List; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public class PropertyValues { private final List propertyValueList = new ArrayList<>(); public PropertyValues() {} public void addPropertyValue(PropertyValue pv) { this.propertyValueList.add(pv); } public List getPropertyValues() { return this.propertyValueList; } } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/annotation/AnnotationAttributes.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.annotation; import io.gamioo.common.util.Assert; import java.lang.annotation.Annotation; import java.lang.reflect.Array; import java.util.LinkedHashMap; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public class AnnotationAttributes extends LinkedHashMap { private final Class annotationType; private final String displayName; /** * Create a new, empty {@link AnnotationAttributes} instance. */ public AnnotationAttributes() { this.annotationType = null; this.displayName = "unknown"; } /** * Create a new, empty {@link AnnotationAttributes} instance for the * specified {@code annotationType}. * * @param annotationType the type of annotation represented by this * {@code AnnotationAttributes} instance; never {@code null} * @since 4.2 */ public AnnotationAttributes(Class annotationType) { Assert.notNull(annotationType, "annotationType must not be null"); this.annotationType = annotationType; this.displayName = annotationType.getName(); } /** * Get the type of annotation represented by this * {@code AnnotationAttributes} instance. * * @return the annotation type, or {@code null} if unknown * @since 4.2 */ public Class annotationType() { return this.annotationType; } /** * Get the value stored under the specified {@code attributeName} as a * string. * * @param attributeName the name of the attribute to get; never * {@code null} or empty * @return the value * @throws IllegalArgumentException if the attribute does not exist or * if it is not of the expected type */ public String getString(String attributeName) { return getRequiredAttribute(attributeName, String.class); } /** * Get the value stored under the specified {@code attributeName}, * ensuring that the value is of the {@code expectedType}. *

If the {@code expectedType} is an array and the value stored * under the specified {@code attributeName} is a single element of the * component type of the expected array type, the single element will be * wrapped in a single-element array of the appropriate type before * returning it. * * @param attributeName the name of the attribute to get; never * {@code null} or empty * @param expectedType the expected type; never {@code null} * @return the value * @throws IllegalArgumentException if the attribute does not exist or * if it is not of the expected type */ @SuppressWarnings("unchecked") private T getRequiredAttribute(String attributeName, Class expectedType) { Assert.hasText(attributeName, "attributeName must not be null or empty"); Object value = get(attributeName); if (!expectedType.isInstance(value) && expectedType.isArray() && expectedType.getComponentType().isInstance(value)) { Object array = Array.newInstance(expectedType.getComponentType(), 1); Array.set(array, 0, value); value = array; } return (T) value; } } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/annotation/AnnotationBeanDefinitionReader.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.annotation; import io.gamioo.ioc.context.ClassPathBeanDefinitionScanner; import io.gamioo.ioc.factory.support.AbstractBeanDefinitionReader; import io.gamioo.ioc.factory.support.DefaultListableBeanFactory; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public class AnnotationBeanDefinitionReader extends AbstractBeanDefinitionReader { private static final Logger logger = LogManager.getLogger(AnnotationBeanDefinitionReader.class); public AnnotationBeanDefinitionReader(DefaultListableBeanFactory beanFactory) { super(beanFactory); } public void loadBeanDefinitions(String location){ // ResourceLoader resourceLoader=this.getResourceLoader(); ClassPathBeanDefinitionScanner scanner=new ClassPathBeanDefinitionScanner(this.getBeanFactory()); //扫描资源 // Actually scan for bean definitions and register them. // this.getClass().getPackage(). scanner.doScan(location, "io.gamioo"); // this.scanPackage(resourceLoader.getLocation(), "io.gamioo"); } // public Set doScan(String basePackage) { // List list= this.getResources(basePackage); // // // return null; // } } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/annotation/Attribute.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * some description * * @author Allen Jiang * @since 1.0.0 */ @Target({ ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) public @interface Attribute { String value(); } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/annotation/Bean.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.annotation; import java.lang.annotation.*; /** * 申明一个IOC容器接管的JavaBean. *

* 只有当在{@link Configuration}注解所标识的类中才会生效. * * @author Allen Jiang * @since 1.0.0 */ @Documented @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface Bean { String value(); } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/annotation/CommandMapping.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * TCP 协议入口 * * @author Allen Jiang * @since 1.0.0 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface CommandMapping { int code() default 0; boolean cross() default false;//是否只跨服处理 boolean print() default true;//是否要打印 /** 是否要做登录验证 */ boolean login() default true; } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/annotation/Configuration.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.annotation; import io.gamioo.ioc.stereotype.Component; import java.lang.annotation.*; /** * 用来标识一个配置文件类. *

* 可与{@link Attribute}来实现注入配置参数.
* 这种类也是只在启动时有用,相当于启动配置文件的作用. * * @author Allen Jiang * @since 1.0.0 */ @Component @Documented @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface Configuration { String value(); } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/annotation/DefaultResourceLoader.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.annotation; import io.gamioo.common.exception.ServerBootstrapException; import io.gamioo.common.util.ClassUtils; import io.gamioo.ioc.io.FileClassResource; import io.gamioo.ioc.io.JarClassResource; import io.gamioo.ioc.io.Resource; import io.gamioo.ioc.io.ResourceLoader; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.io.File; import java.io.IOException; import java.net.JarURLConnection; import java.net.URL; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarFile; /** * 资源读取器 * * @author Allen Jiang * @since 1.0.0 */ public class DefaultResourceLoader implements ResourceLoader { private static final Logger logger = LogManager.getLogger(DefaultResourceLoader.class); private static final String CLASS_SUFFIX = ".class"; private static final String PACKAGE_INFO_CLASS = "package-info.class"; /** * URL protocol for a file in the file system: "file" */ private static final String URL_PROTOCOL_FILE = "file"; /** * URL protocol for an entry from a jar file: "jar" */ private static final String URL_PROTOCOL_JAR = "jar"; @Override public List getResourceList(String basePackage) { List ret = new ArrayList<>(); // 处理一下包名到目录 basePackage = basePackage.replace('.', '/').replace('\\', '/').concat("/"); try { Enumeration list = ClassUtils.getDefaultClassLoader().getResources(basePackage); while (list.hasMoreElements()) { URL url = list.nextElement(); switch (url.getProtocol()) { // "file" case URL_PROTOCOL_FILE: { ret.addAll(this.doFindFileResources(basePackage, new File(url.getFile()))); break; } // "jar" case URL_PROTOCOL_JAR: { ret.addAll(this.doFindJarResources(url)); break; } default: break; } } //TODO ... } catch (IOException e) { throw new ServerBootstrapException(e, "scan process failed,basePackages={}", basePackage); } return ret; } /** * 递归扫描目录文件. * * @param file 文件 */ private List doFindFileResources(String basePackage, File file) { List ret = new ArrayList<>(); String path = file.getAbsolutePath(); // 这个目录不存在,忽略 if (!file.exists()) { logger.debug("path:{} does not exist", path); return ret; } // 这个目录不可以读,忽略 if (!file.canRead()) { logger.warn("the directory {} can't be read", path); return ret; } ret = this.listFiles(basePackage, file); return ret; } /** * 查找到一个目录. * * @param file 目录 */ private List listFiles(String basePackage, File file) { List ret = new ArrayList<>(); if (file.isDirectory()) { File[] array = file.listFiles(); if (array != null) { for (File e : array) { if (e.isFile()) { String fileName = file.getName(); // 忽略 package-info.class if (fileName.equals(PACKAGE_INFO_CLASS)) { continue; } // 忽略非Class文件 if (fileName.endsWith(CLASS_SUFFIX)) { continue; } Resource resource = getFile(basePackage, e); ret.add(resource); } else if (e.isDirectory()) { List list = listFiles(basePackage + e.getName() + "/", e); ret.addAll(list); } } } } else if (file.isFile()) { Resource resource = getFile(basePackage, file); ret.add(resource); } return ret; } /** * 查找到一个文件. * * @param file 文件 * @return ResourceCallback */ private Resource getFile(String basePackage, File file) { return new FileClassResource(basePackage, file); } private List doFindJarResources(URL url) throws IOException { List ret = new ArrayList<>(); JarURLConnection jarCon = (JarURLConnection) url.openConnection(); try (JarFile jarFile = jarCon.getJarFile()) { for (Enumeration entries = jarFile.entries(); entries.hasMoreElements(); ) { JarEntry entry = entries.nextElement(); if (entry.getName().endsWith(CLASS_SUFFIX)) { Resource resource = getJarFile(entry.getName()); ret.add(resource); } } } return ret; } /** * 查找到一个Jar文件. * * @param path Jar的资源路径 */ private Resource getJarFile(String path) { return new JarClassResource(path); } } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/annotation/Mapping.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * some description * * @author Allen Jiang * @since 1.0.0 */ @Target({ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface Mapping { } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/annotation/RequestMapping.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.annotation; import io.gamioo.common.http.RequestMethod; import java.lang.annotation.*; /** * HTTP 协议入口 * * @author Allen Jiang * @since 1.0.0 */ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Mapping public @interface RequestMapping { String name() default ""; String value() default ""; /** * The HTTP request methods to map to, narrowing the primary mapping: * GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE. *

Supported at the type level as well as at the method level! * When used at the type level, all method-level mappings inherit * this HTTP method restriction (i.e. the type-level restriction * gets checked before the handler method is even resolved). *

Supported for Servlet environments as well as Portlet 2.0 environments. */ RequestMethod[] method() default {}; } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/annotation/Scheduled.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) public @interface Scheduled { int type(); } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/annotation/Subscribe.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * some description * * @author Allen Jiang * @since 1.0.0 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Subscribe {} ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/annotation/Value.java ================================================ package io.gamioo.ioc.annotation; import java.lang.annotation.*; /** * 注入配置文件中的属性. * * @author Allen Jiang * @since 1.0.0 */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD}) public @interface Value { /** * 属性配置文件中的配置键值. * * @return Key. */ String value(); } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/config/BeanFactoryPostProcessor.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.config; /** * 用于修改一些bean的定义和值 * * @author Allen Jiang * @since 1.0.0 */ public interface BeanFactoryPostProcessor { // void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws ServiceException; } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/config/BeanPostProcessor.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.config; import io.gamioo.common.exception.BeansException; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public interface BeanPostProcessor { Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException; Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException; } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/config/BeanReference.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.config; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public class BeanReference { private String name; private Object bean; public BeanReference(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Object getBean() { return bean; } public void setBean(Object bean) { this.bean = bean; } } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/context/ApplicationContext.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.context; import io.gamioo.ioc.factory.support.DefaultListableBeanFactory; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public interface ApplicationContext{ DefaultListableBeanFactory createBeanFactory(); void loadBeanDefinitions(DefaultListableBeanFactory beanFactory); public T getBean(Class requiredType); } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/context/ClassPathBeanDefinitionScanner.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.context; import io.gamioo.common.util.AnnotationUtils; import io.gamioo.common.util.Assert; import io.gamioo.common.util.ClassUtils; import io.gamioo.ioc.annotation.Configuration; import io.gamioo.ioc.annotation.DefaultResourceLoader; import io.gamioo.ioc.definition.ConfigurationBeanDefinition; import io.gamioo.ioc.definition.ControllerBeanDefinition; import io.gamioo.ioc.definition.GenericBeanDefinition; import io.gamioo.ioc.factory.BeanFactory; import io.gamioo.ioc.io.Resource; import io.gamioo.ioc.stereotype.Component; import io.gamioo.ioc.stereotype.Controller; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.lang.annotation.Annotation; import java.lang.reflect.Modifier; import java.util.List; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public class ClassPathBeanDefinitionScanner { private static final Logger logger = LogManager.getLogger(ClassPathBeanDefinitionScanner.class); private DefaultResourceLoader resourceLoader = new DefaultResourceLoader(); private BeanFactory beanFactory; public ClassPathBeanDefinitionScanner(BeanFactory beanFactory) { this.beanFactory = beanFactory; // this.resourceLoader = resourceLoader; } public void doScan(String... basePackages) { Assert.notEmpty(basePackages, "At least one base package must be specified"); for (String e : basePackages) { this.analysisResourceList(e); } } /** * 解析加载进来的资源 * * @param location 资源的地址 */ public void analysisResourceList(String location) { List list = this.resourceLoader.getResourceList(location); for (Resource e : list) { this.analysisResource(e); } } public void analysisResource(Resource resource) { String className = resource.getClassName(); Class clazz = ClassUtils.loadClass(className); //TODO ... analysisClass(clazz); } /** * 解析Class,处理游戏定义 * * @param klass Class */ private void analysisClass(Class klass) { // 接口、内部类、枚举、注解和匿名类 直接忽略 if (klass.isInterface() || klass.isMemberClass() || klass.isEnum() || klass.isAnnotation() || klass.isAnonymousClass()) { return; } // 抽象类和非Public的也忽略 int modify = klass.getModifiers(); if (Modifier.isAbstract(modify) || (!Modifier.isPublic(modify))) { return; } // 查找带有@Component注解或其他注解上有@Component注解的类 Annotation annotation = AnnotationUtils.getAnnotation(klass, Component.class); if (annotation == null) { return; } Class type = annotation.annotationType(); // 配置类 if (type == Configuration.class) { // configurations.add(new ConfigurationBeanDefinition(klass,annotation)); beanFactory.registerBeanDefinition(klass.getSimpleName(), new ConfigurationBeanDefinition(klass, annotation)); } else if (type == Controller.class) { beanFactory.registerBeanDefinition(klass.getSimpleName(), new ControllerBeanDefinition(klass, annotation)); } else { //不需要处理循环引用的问题,只要不同的定义放到不同的集合里就行 beanFactory.registerBeanDefinition(klass.getSimpleName(), new GenericBeanDefinition(klass, annotation)); } // // 模板转化器. // else if (annotationType == TemplateConverter.class) { // ConvertManager.getInstance().register(klass, (TemplateConverter) annotation); // } // // 协议入口控制类 // else if (annotationType == Controller.class) { // analytical(klass, (Controller) annotation); // } // // 协议入口控制类(模块化) // else if (annotationType == ModuleController.class) { // analytical(klass, (ModuleController) annotation); // } // // 静态组件 // else if (annotationType == StaticComponent.class) { // staticComponents.add(new StaticComponentBeanDefinition(klass).init()); // } // // 不是已定义的,那就扫描这个注解上有没有@Component // else { // beans.put(klass, new DefaultBeanDefinition(klass, annotation, annotationType).init()); // } } } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/context/ConfigApplicationContext.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.context; import io.gamioo.ioc.annotation.AnnotationBeanDefinitionReader; import io.gamioo.ioc.definition.ControllerBeanDefinition; import io.gamioo.ioc.factory.support.DefaultListableBeanFactory; import io.gamioo.ioc.wrapper.Command; import java.lang.annotation.Annotation; import java.util.Collection; import java.util.List; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public class ConfigApplicationContext implements ApplicationContext { private String location; private DefaultListableBeanFactory beanFactory; public ConfigApplicationContext(String location) { this.location = location; this.refresh(); } /** * TODO ... */ public void refresh() { //注入属性实例化等 DefaultListableBeanFactory factory = this.refreshBeanFactory(); prepareBeanFactory(factory); // Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(factory); } public DefaultListableBeanFactory refreshBeanFactory() { DefaultListableBeanFactory factory = this.createBeanFactory(); this.loadBeanDefinitions(factory); return factory; } /** * 做一些beanFactory的属性设置工作 */ protected void prepareBeanFactory(DefaultListableBeanFactory beanFactory) { //TODO ... } /** * 实例化操作 */ protected void finishBeanFactoryInitialization(DefaultListableBeanFactory beanFactory) { beanFactory.preInstantiateSingletons(); } public String getLocation() { return location; } @Override public DefaultListableBeanFactory createBeanFactory() { return new DefaultListableBeanFactory(); } @Override public void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) { this.beanFactory = beanFactory; AnnotationBeanDefinitionReader reader = new AnnotationBeanDefinitionReader(beanFactory); reader.loadBeanDefinitions(location); } public T getBean(Class requiredType) { return this.beanFactory.getBean(requiredType); } public List getBeanListOfType(Class type) { return this.beanFactory.getBeanListOfType(type); } public List getBeanListOfAnnotation(Class annotation) { return this.beanFactory.getBeanListOfAnnotation(annotation); } public Command getCommand(int code) { return ControllerBeanDefinition.getCommand(code); } public Collection getCommandList() { return ControllerBeanDefinition.getCommandList(); } // public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry { // // private final AnnotatedBeanDefinitionReader reader; // // private final ClassPathBeanDefinitionScanner scanner; } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/definition/BeanDefinition.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.definition; import java.lang.annotation.Annotation; import java.util.List; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public interface BeanDefinition extends Definition { List getFieldDefinitionList(Class clazz); List getMethodDefinitionList(Class clazz); // List getValueFieldDefinition(); Object newInstance(); /** * 解析方法 */ void analysisMethodList(); /** * 解析字段 */ void analysisFieldList(); /** * 解析实体对象 * * @param instance 对象 */ void analysisBean(Object instance); // /** // * 注入 // */ // void inject(Object instance); MethodDefinition getInitMethodDefinition(); // void setInitMethodName(String initMethodName); // // String getDestroyMethodName(); // // void setDestroyMethodName(String destroyMethodName); // String getBeanClassName(); // // void setBeanClassName(String beanClassName); // // Class getBeanClass(); // void setBeanClass(Class beanClass); // // public PropertyValues getPropertyValues(); // // void setPropertyValues(PropertyValues propertyValues); } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/definition/ConfigurationBeanDefinition.java ================================================ package io.gamioo.ioc.definition; import com.alibaba.fastjson2.JSONObject; import io.gamioo.common.util.JSONUtils; import io.gamioo.common.util.StringUtils; import io.gamioo.ioc.annotation.Configuration; import javax.annotation.PostConstruct; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 配置文件解析定义 * * @author Allen Jiang * @since 1.0.0 */ public class ConfigurationBeanDefinition implements BeanDefinition { protected final Class beanClass; protected final Configuration annotation; protected Map, List> methodStore = new HashMap<>(); public ConfigurationBeanDefinition(Class clazz, Annotation annotation) { this.beanClass = clazz; this.annotation = (Configuration) annotation; } /** * 每次都可以从磁盘加载 */ @Override public Object newInstance() { String fileName = this.annotation.value(); JSONObject object = JSONUtils.loadFromXMLFile(fileName); return object.to(this.beanClass); } @Override public List getFieldDefinitionList(Class clazz) { return new ArrayList<>(); } @Override public List getMethodDefinitionList(Class clazz) { return methodStore.getOrDefault(clazz, new ArrayList<>()); } /** * 解析方法 */ @Override public void analysisMethodList() { } /** * 解析字段 */ @Override public void analysisFieldList() { } /** * 解析实体对象 * * @param instance */ @Override public void analysisBean(Object instance) { //TODO ... } // // /** // * 注入 // */ // @Override // public void inject() { // // } @Override public MethodDefinition getInitMethodDefinition() { MethodDefinition ret = null; List list = this.getMethodDefinitionList(PostConstruct.class); if (list != null && list.size() > 0) { ret = list.get(0); } return ret; } @Override public Class getClazz() { return this.beanClass; } @Override public String getName() { return StringUtils.uncapitalized(this.beanClass.getSimpleName()); } /** * 获取注解 */ @Override public Annotation getAnnotation() { return this.annotation; } /** * 获取注解类型 */ @Override public Class getAnnotationType() { return this.getAnnotation().annotationType(); } /** * 获取此方法的访问入口所对应的Index. * * @return 访问入口所对应的Index. */ @Override public int getIndex() { return 0; } } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/definition/ControllerBeanDefinition.java ================================================ package io.gamioo.ioc.definition; import io.gamioo.ioc.annotation.CommandMapping; import io.gamioo.ioc.annotation.RequestMapping; import io.gamioo.ioc.stereotype.Controller; import io.gamioo.ioc.wrapper.Command; import io.gamioo.ioc.wrapper.MethodWrapper; import java.lang.annotation.Annotation; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * 控制器入口的定义 * * @author Allen Jiang * @since 1.0.0 */ public class ControllerBeanDefinition extends GenericBeanDefinition{ /** * 控制器的消息处理容器 */ protected final static Map commandStore = new ConcurrentHashMap<>(1024); public ControllerBeanDefinition(Class clazz, Annotation annotation) { super(clazz, annotation); } /** * 解析实体对象 * * @param instance */ @Override public void analysisBean(Object instance) { //控制器入口有特殊的分析 // Class type =this.getAnnotationType(); // // 控制器 // //TODO ... 后续可以把这些解析放到 gamioo-game 中,开发者可以自定义注释,如何解析注释以及如何使用注释 // if (type == Controller.class) { //@MessageMapping List methodList = this.getMethodDefinitionList(CommandMapping.class); for (MethodDefinition e : methodList) { CommandMapping mapping = e.getAnnotation(); MethodWrapper wrapper = e.getMethodWrapper(instance); Command command = new Command(wrapper, mapping); this.commandStore.put(command.getCode(), command); } //@RequestMapping methodList = this.getMethodDefinitionList(RequestMapping.class); for (MethodDefinition e : methodList) { RequestMapping mapping = e.getAnnotation(); //TODO ... } // // } else { // // } } /**根据指令调用*/ public static Command getCommand(int code) { return commandStore.get(code); } /**指令列表*/ public static Collection getCommandList(){ return commandStore.values(); } } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/definition/Definition.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.definition; import java.lang.annotation.Annotation; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public interface Definition { Class getClazz(); String getName(); /** * 获取注解 * * @return 返回注解 */ T getAnnotation(); /** * 获取注解 * * @return 返回注解类型 */ Class getAnnotationType(); /** * 获取此方法的访问入口所对应的Index. * * @return 访问入口所对应的Index. */ int getIndex(); } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/definition/FieldDefinition.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.definition; import java.lang.reflect.Field; /** * 一个字段定义 * * @author Allen Jiang * @since 1.0.0 */ public interface FieldDefinition extends Definition{ /** * 注入对象. * * @param instance 宿主对象 */ void inject(Object instance,Object value); Field getField(); } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/definition/GenericBeanDefinition.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.definition; import com.esotericsoftware.reflectasm.FieldAccess; import com.esotericsoftware.reflectasm.MethodAccess; import io.gamioo.common.util.ClassUtils; import io.gamioo.common.util.FieldUtils; import io.gamioo.common.util.MethodUtils; import io.gamioo.common.util.StringUtils; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.*; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public class GenericBeanDefinition implements BeanDefinition { private static final Set> IGNORE_ANNOTATION_BY_METHODS = new HashSet<>(); static { IGNORE_ANNOTATION_BY_METHODS.add(Deprecated.class); } // protected Object instance; private final Map, List> fieldStore = new HashMap<>(); protected Map, List> methodStore = new HashMap<>(); private final Class beanClass; private final Annotation annotation; public GenericBeanDefinition(Class clazz, Annotation annotation) { this.beanClass = clazz; this.annotation = annotation; this.analysisFieldList(); this.analysisMethodList(); } /** * 解析方法 */ @Override public void analysisMethodList() { MethodAccess access = MethodAccess.get(beanClass); List list = MethodUtils.getMethodList(beanClass); for (Method method : list) { Annotation[] array = method.getAnnotations(); if (array != null && array.length > 0) { for (Annotation annotation : array) { Class annotationType = annotation.annotationType(); if (IGNORE_ANNOTATION_BY_METHODS.contains(annotationType)) { continue; } this.analysisMethod(annotationType, access, method); } } } } private void analysisMethod(Class annotationType, MethodAccess access, Method method) { methodStore.computeIfAbsent(annotationType, key -> new ArrayList<>(64)).add(new GenericMethodDefinition(access, method)); } @Override public void analysisFieldList() { FieldAccess access = FieldAccess.get(beanClass); List list = FieldUtils.getFieldList(beanClass); for (Field field : list) { Annotation[] array = field.getAnnotations(); for (Annotation annotation : array) { Class annotationType = annotation.annotationType(); this.analysisField(annotationType, access, field); } } } /** * 解析实体对象 异步事件等 * * @param instance */ @Override public void analysisBean(Object instance) { // } private void analysisField(Class annotationType, FieldAccess access, Field field) { Class clazz = field.getType(); if (clazz == List.class) { fieldStore.computeIfAbsent(annotationType, key -> new ArrayList<>()).add(new ListFieldDefinition(field)); } else if (clazz == Map.class) { fieldStore.computeIfAbsent(annotationType, key -> new ArrayList<>()).add(new MapFieldDefinition(field)); } else { fieldStore.computeIfAbsent(annotationType, key -> new ArrayList<>(64)).add(new GenericFieldDefinition(field)); } } // @Override // public void inject(Object instance){ // } @Override public List getFieldDefinitionList(Class clazz) { return fieldStore.getOrDefault(clazz, new ArrayList<>()); } @Override public List getMethodDefinitionList(Class clazz) { return methodStore.getOrDefault(clazz, new ArrayList<>()); } /** * 初始化的方法 */ @Override public MethodDefinition getInitMethodDefinition() { MethodDefinition ret = null; List list = methodStore.get(PostConstruct.class); if (list != null && list.size() > 0) { ret = list.get(0); } return ret; } /** * 销毁的方法 */ public MethodDefinition getDestroyMethodDefinition() { MethodDefinition ret = null; List list = methodStore.get(PreDestroy.class); if (list != null && list.size() > 0) { ret = list.get(0); } return ret; } @Override public Object newInstance() { return ClassUtils.newInstance(this.beanClass); } // /** // * 注入 // */ // @Override // public void inject() { // // fieldList.values().forEach(v -> v.forEach(field -> field.inject(this))); // // } // // public List getAutowiredFieldDefinitionList() { // List list = fieldStore.getOrDefault(Autowired.class, new ArrayList<>()); // return list; // } // public List getValueFieldDefinition() { // List list = fieldStore.get(Value.class); // return list; // } @Override public Class getClazz() { return this.beanClass; } @Override public String getName() { return StringUtils.uncapitalized(this.beanClass.getSimpleName()); } /** * 获取注解 */ @Override public Annotation getAnnotation() { return this.annotation; } /** * 获取注解类型 */ @Override public Class getAnnotationType() { return this.getAnnotation().annotationType(); } /** * 获取此方法的访问入口所对应的Index. * * @return 访问入口所对应的Index. */ @Override public int getIndex() { return 0; } } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/definition/GenericFieldDefinition.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.definition; import io.gamioo.common.util.FieldUtils; import java.lang.annotation.Annotation; import java.lang.reflect.Field; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public class GenericFieldDefinition implements FieldDefinition { private Field field; public GenericFieldDefinition(Field field) { this.field = field; // this.field.setAccessible(true); } @Override public Annotation getAnnotation(){ return this.field.getDeclaredAnnotations()[0]; } @Override public Class getClazz() { Class clazz= field.getType().getClass(); return clazz; } @Override public Field getField() { return field; } @Override public String getName() { return this.field.getName(); } /** * 获取注解类型 */ @Override public Class getAnnotationType() { return this.getAnnotation().annotationType(); } /** * 注入对象. * * @param instance 宿主对象 */ @Override public void inject(Object instance,Object value) { //TODO ... FieldUtils.writeField(instance, field, value); } /** * 获取此方法的访问入口所对应的Index. * * @return 访问入口所对应的Index. */ @Override public int getIndex() { return 0; } } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/definition/GenericMethodDefinition.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.definition; import com.esotericsoftware.reflectasm.MethodAccess; import io.gamioo.ioc.wrapper.MethodWrapper; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.lang.reflect.Parameter; /** * 通用的法的基本定义类 * * @author Allen Jiang * @since 1.0.0 */ public class GenericMethodDefinition implements MethodDefinition { private int index; private Method method; private MethodAccess access; private Parameter[] parameters; public GenericMethodDefinition(MethodAccess access, Method method) { this.method = method; this.access = access; this.index = access.getIndex(method.getName(), method.getParameterTypes()); this.parameters = method.getParameters(); this.method.setAccessible(true); } @Override public Annotation getAnnotation() { return this.method.getDeclaredAnnotations()[0]; } @Override public Class getClazz() { return method.getGenericReturnType().getClass(); } @Override public String getName() { return this.method.getName(); } /** * 获取注解 */ @Override public Class getAnnotationType() { return this.getAnnotation().annotationType(); } /** * 获取此方法的访问入口. * * @return 访问入口 */ @Override public MethodAccess getMethodAccess() { return access; } /** * 获取此方法的访问入口所对应的Index. * * @return 访问入口所对应的Index. */ @Override public int getIndex() { return index; } public Object invoke(Object instance, Object... args) { Object ret = null; if (args == null) { // 无参数调用 ret = access.invoke(instance, index); } else { // 有参数调用 ret = access.invoke(instance, index, args); } return ret; } public MethodWrapper getMethodWrapper(Object instance) { MethodWrapper ret = new MethodWrapper(instance, method, access, index); return ret; } /** * 获取参数列表 * * @return 参数 */ @Override public Parameter[] getParameters() { return parameters; } } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/definition/ListFieldDefinition.java ================================================ package io.gamioo.ioc.definition; import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; /** * 所有实现此接口或继承此类的 * * @author Allen Jiang * @since 1.0.0 */ public class ListFieldDefinition extends GenericFieldDefinition { public ListFieldDefinition(Field field) { super(field); } @Override public Class getClazz() { Class ret = (Class) ((ParameterizedType) this.getField().getGenericType()).getActualTypeArguments()[0]; return ret; } } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/definition/MapFieldDefinition.java ================================================ package io.gamioo.ioc.definition; import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; /** * Map类型的属性注入 * * @author Allen Jiang * @since 1.0.0 */ public class MapFieldDefinition extends GenericFieldDefinition { public MapFieldDefinition(Field field) { super(field); } @Override public Class getClazz() { Class ret = (Class) ((ParameterizedType) this.getField().getGenericType()).getActualTypeArguments()[1]; return ret; } } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/definition/MethodDefinition.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.definition; import com.esotericsoftware.reflectasm.MethodAccess; import io.gamioo.ioc.wrapper.MethodWrapper; import java.lang.reflect.Parameter; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public interface MethodDefinition extends Definition{ /** * 获取此方法的访问入口. * * @return 访问入口 */ MethodAccess getMethodAccess(); /** * 获取参数列表 * * @return 参数 */ Parameter[] getParameters(); Object invoke(Object instance,Object ...args); MethodWrapper getMethodWrapper(Object instance); } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/definition/ResourceBeanDefinition.java ================================================ package io.gamioo.ioc.definition; import java.lang.annotation.Annotation; /** * 资源解析管理器的定义 * * @author Allen Jiang * @since 1.0.0 */ public class ResourceBeanDefinition extends GenericBeanDefinition{ public ResourceBeanDefinition(Class clazz, Annotation annotation) { super(clazz, annotation); } } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/factory/BeanFactory.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.factory; import io.gamioo.common.exception.BeansException; import io.gamioo.ioc.definition.BeanDefinition; import java.lang.annotation.Annotation; import java.util.List; import java.util.Map; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public interface BeanFactory { public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition); public T getBean(Class requiredType); public Object getBean(String name) throws BeansException; public List getBeanListOfType(Class type); public List getBeanListOfAnnotation(Class type); public T getBean(String name, Class requiredType); public Map getBeanMapOfType(Class type); void preInstantiateSingletons(); } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/factory/ObjectFactory.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.factory; import io.gamioo.common.exception.BeansException; /** * Defines a factory which can return an Object instance * (possibly shared or independent) when invoked. * *

This interface is typically used to encapsulate a generic factory which * returns a new instance (prototype) of some target object on each invocation. * * @author Allen Jiang * @since 1.0.0 */ public interface ObjectFactory { /** * Return an instance (possibly shared or independent) * of the object managed by this factory. * @return the resulting instance * @throws BeansException in case of creation errors */ T getObject() throws BeansException; } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/factory/annotation/Autowired.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.factory.annotation; import java.lang.annotation.*; /** * some description * * @author Allen Jiang * @since 1.0.0 */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Autowired { /** * Declares whether the annotated dependency is required. *

Defaults to {@code true}. */ boolean required() default true; } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/factory/support/AbstractAutowireCapableBeanFactory.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.factory.support; import io.gamioo.ioc.definition.*; import io.gamioo.ioc.factory.annotation.Autowired; import java.util.List; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory { @Override Object createBean(BeanDefinition beanDefinition) { return doCreateBean(beanDefinition); } Object doCreateBean(BeanDefinition beanDefinition) { String beanName = beanDefinition.getName(); Object instance = createBeanInstance(beanDefinition); // Eagerly cache singletons to be able to resolve circular references // even when triggered by lifecycle interfaces like BeanFactoryAware. boolean earlySingletonExposure = isSingletonCurrentlyInCreation(beanName); if (earlySingletonExposure) { addSingletonFactory(beanName, () -> getEarlyBeanReference(instance)); } //填充实例,这里完成了循环引用的问题 populateBean(instance, beanDefinition); //调用类的初始化方法 initializeBean(instance, beanDefinition); //解析对象 控制指令,异步事件等额外处理 beanDefinition.analysisBean(instance); return instance; } @Override protected Object createBeanInstance(BeanDefinition beanDefinition) { return instantiateBean(beanDefinition); } /** * 初始化BEAN * * @param beanDefinition bean定义 * @return 返回对象 */ protected Object instantiateBean(BeanDefinition beanDefinition) { return beanDefinition.newInstance(); } /** * 填充bean,并做好互相填充的动作,是否需要在这里处理autowire的扫描呢?todo.... */ @Override protected void populateBean(Object instance, BeanDefinition beanDefinition) { //处理 @Autowire 注解的字段 List fieldList = beanDefinition.getFieldDefinitionList(Autowired.class); for (FieldDefinition e : fieldList) { Object field = null; if (e instanceof ListFieldDefinition) { field = this.getBeanListOfType(e.getClazz()); } else if (e instanceof MapFieldDefinition) { field = this.getBeanMapOfType(e.getClazz()); } else if (e instanceof GenericFieldDefinition) { field = this.getBean(e.getName()); } e.inject(instance, field); } } // @Override // Object doCreateBean(BeanDefinition beanDefinition) throws Exception { // Object bean = beanDefinition.getBeanClass().newInstance(); // beanDefinition.setBean(bean); // applyPropertyValues(bean, beanDefinition); // return bean; // } // void applyPropertyValues(BeanWrapper beanWrapper, BeanDefinition beanDefinition) throws Exception { // for (PropertyValue propertyValue : beanDefinition.getPropertyValues().getPropertyValues()) { // Field field = beanDefinition.getBeanClass().getDeclaredField(propertyValue.getName()); // field.setAccessible(true); // field.set(beanWrapper.getWrappedInstance(), propertyValue.getValue()); // } // } @Override protected void initializeBean(Object object, BeanDefinition beanDefinition) { this.invokeInitMethods(object, beanDefinition); } /** * 调用PostConstruct方法 */ private void invokeInitMethods(Object object, BeanDefinition beanDefinition) { MethodDefinition method = beanDefinition.getInitMethodDefinition(); if (method != null) { method.invoke(object); } } } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/factory/support/AbstractBeanDefinition.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.factory.support; import com.esotericsoftware.reflectasm.MethodAccess; import io.gamioo.ioc.PropertyValues; import io.gamioo.ioc.definition.BeanDefinition; import java.lang.annotation.Annotation; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public abstract class AbstractBeanDefinition implements BeanDefinition { private MethodAccess access; private String initMethodName; private String destroyMethodName; private Object bean;// 对象实例 private Class beanClass;//bean的类型 private String beanClassName;//类名 private Annotation annotation; private Class annotationType; private PropertyValues propertyValues; public Object getBean() { return bean; } public void setBean(Object bean) { this.bean = bean; } public Class getBeanClass() { return beanClass; } public Annotation getAnnotation() { return annotation; } public void setAnnotation(Annotation annotation) { this.annotation = annotation; } public Class getAnnotationType() { return annotationType; } public void setAnnotationType(Class annotationType) { this.annotationType = annotationType; } public String getInitMethodName() { return initMethodName; } public void setInitMethodName(String initMethodName) { this.initMethodName = initMethodName; } public String getDestroyMethodName() { return destroyMethodName; } public void setDestroyMethodName(String destroyMethodName) { this.destroyMethodName = destroyMethodName; } public void setBeanClass(Class beanClass) { this.beanClass = beanClass; } public String getBeanClassName() { return beanClassName; } public void setBeanClassName(String beanClassName) { this.beanClassName = beanClassName; try { this.beanClass = Class.forName(beanClassName); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public PropertyValues getPropertyValues() { if (propertyValues == null) { propertyValues = new PropertyValues(); } return propertyValues; } public void setPropertyValues(PropertyValues propertyValues) { this.propertyValues = propertyValues; } } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/factory/support/AbstractBeanDefinitionReader.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.factory.support; import io.gamioo.ioc.factory.BeanFactory; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader { // private Map registry= new HashMap(); // private ResourceLoader resourceLoader; private BeanFactory beanFactory; public AbstractBeanDefinitionReader(BeanFactory beanFactory) { this.beanFactory = beanFactory; } public BeanFactory getBeanFactory() { return beanFactory; } public void setBeanFactory(BeanFactory beanFactory) { this.beanFactory = beanFactory; } // public Map getRegistry() { // return registry; // } // public void setResourceLoader(ResourceLoader resourceLoader) { // this.resourceLoader = resourceLoader; // } // // public ResourceLoader getResourceLoader() { // return resourceLoader; // } } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/factory/support/AbstractBeanFactory.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.factory.support; import io.gamioo.common.exception.BeansException; import io.gamioo.common.exception.ServerBootstrapException; import io.gamioo.common.util.StringUtils; import io.gamioo.ioc.definition.BeanDefinition; import io.gamioo.ioc.factory.BeanFactory; import io.gamioo.ioc.factory.ObjectFactory; import io.gamioo.ioc.stereotype.Component; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.lang.annotation.Annotation; import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public abstract class AbstractBeanFactory implements BeanFactory { private static final Logger logger = LogManager.getLogger(AbstractBeanFactory.class); /** * Map of bean definition objects, keyed by bean name. */ protected final Map beanDefinitionMap = new ConcurrentHashMap<>(128); /** * Cache of singleton objects: bean name --> bean instance 一级缓存 */ private final Map singletonObjects = new ConcurrentHashMap<>(256); /** * Cache of early singleton objects: bean name --> bean instance 二级缓存 */ private final Map earlySingletonObjects = new HashMap<>(16); /** * Cache of singleton factories: bean name --> ObjectFactory 三级缓存,主要为了解决AOP代理类才存在的 */ private final Map> singletonFactories = new HashMap<>(16); /** * Set of registered singletons, containing the bean names in registration order. */ private final Set registeredSingletons = new LinkedHashSet<>(256); /** * Names of beans that are currently in creation. */ private final Set singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16)); @SuppressWarnings("unchecked") @Override public T getBean(Class requiredType) { return (T) singletonObjects.get(StringUtils.uncapitalized(requiredType.getSimpleName())); } @Override public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) { beanDefinitionMap.put(StringUtils.uncapitalized(beanName), beanDefinition); } /** * 实例化单例 */ @Override public void preInstantiateSingletons() { for (BeanDefinition e : beanDefinitionMap.values()) { getBean(e.getName()); } } @Override public Object getBean(String name) throws BeansException { return doGetBean(name); } @SuppressWarnings("unchecked") protected T doGetBean(String name) throws BeansException { BeanDefinition beanDefinition = beanDefinitionMap.get(name); // Eagerly check singleton cache for manually registered singletons. Object sharedInstance = getSingleton(name); if (sharedInstance == null) { sharedInstance = this.getSingleton(name, () -> createBean(beanDefinition)); } return (T) sharedInstance; } @SuppressWarnings("unchecked") @Override public List getBeanListOfType(Class type) { List ret = new ArrayList<>(); Collection list = beanDefinitionMap.values(); for (BeanDefinition e : list) { if (type.isAssignableFrom(e.getClazz())) { T instance = (T) this.getBean(e.getName()); ret.add(instance); } } return ret; } @SuppressWarnings("unchecked") @Override public Map getBeanMapOfType(Class type) { Map ret = new HashMap<>(); Collection list = beanDefinitionMap.values(); for (BeanDefinition e : list) { if (type.isAssignableFrom(e.getClazz())) { T instance = (T) this.getBean(e.getName()); Component annotation = e.getAnnotation(); ret.put(annotation.name()[0], instance); } } return ret; } /** * 通过注解类型获得对象集合 */ @SuppressWarnings("unchecked") @Override public List getBeanListOfAnnotation(Class annotation) { List ret = new ArrayList<>(); Collection list = beanDefinitionMap.values(); for (BeanDefinition e : list) { if (e.getAnnotationType() == annotation) { T instance = (T) this.getBean(e.getName()); ret.add(instance); } } return ret; } @SuppressWarnings("unchecked") @Override public T getBean(String name, Class requiredType) { Object bean = getBean(name); if (requiredType != null && !requiredType.isInstance(bean)) { throw new ServerBootstrapException(name, requiredType, bean.getClass()); } return (T) bean; } // public void registerSingleton(String beanName, Object singletonObject) throws IllegalStateException { // Assert.notNull(beanName, "Bean name must not be null"); // Assert.notNull(singletonObject, "Singleton object must not be null"); // synchronized (this.singletonObjects) { // Object oldObject = this.singletonObjects.get(beanName); // if (oldObject != null) { // throw new IllegalStateException("Could not register object [" + singletonObject + // "] under bean name '" + beanName + "': there is already object [" + oldObject + "] bound"); // } // addSingleton(beanName, singletonObject); // } // } protected void addSingleton(String beanName, Object singletonObject) { synchronized (this.singletonObjects) { this.singletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } } /** * Add the given singleton factory for building the specified singleton * if necessary. *

To be called for eager registration of singletons, e.g. to be able to * resolve circular references. * * @param beanName the name of the bean * @param singletonFactory the factory for the singleton object */ protected void addSingletonFactory(String beanName, ObjectFactory singletonFactory) { synchronized (this.singletonObjects) { if (!this.singletonObjects.containsKey(beanName)) { this.singletonFactories.put(beanName, singletonFactory); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } } } /** * 获取单例 * * @param beanName bean名称 * @return 返回单例对象 */ protected Object getSingleton(String beanName) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null) { ObjectFactory singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return singletonObject; } public Object getSingleton(String beanName, ObjectFactory singletonFactory) { synchronized (this.singletonObjects) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { beforeSingletonCreation(beanName); singletonObject = singletonFactory.getObject(); afterSingletonCreation(beanName); addSingleton(beanName, singletonObject); } return singletonObject; } } public void destroySingleton(String beanName) { // Remove a registered singleton of the given name, if any. removeSingleton(beanName); } /** * Remove the bean with the given name from the singleton cache of this factory, * to be able to clean up eager registration of a singleton if creation failed. * * @param beanName the name of the bean */ protected void removeSingleton(String beanName) { synchronized (this.singletonObjects) { this.singletonObjects.remove(beanName); this.singletonFactories.remove(beanName); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.remove(beanName); } } /** * @param beanName the name of the bean * @return Return whether the specified singleton bean is currently in creation(within the entire factory). */ public boolean isSingletonCurrentlyInCreation(String beanName) { return this.singletonsCurrentlyInCreation.contains(beanName); } /** * Callback before singleton creation. *

The default implementation register the singleton as currently in creation. * * @param beanName the name of the singleton about to be created * @see #isSingletonCurrentlyInCreation */ protected void beforeSingletonCreation(String beanName) { this.singletonsCurrentlyInCreation.add(beanName); } /** * Callback after singleton creation. *

The default implementation marks the singleton as not in creation anymore. * * @param beanName the name of the singleton that has been created * @see #isSingletonCurrentlyInCreation */ protected void afterSingletonCreation(String beanName) { this.singletonsCurrentlyInCreation.remove(beanName); } protected Object getEarlyBeanReference(Object bean) { return bean; } // // public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) { // // 何时设置beanDefinition的其他属性beanClass,beanClassName?——在BeanDefinitionReader加载xml文件的时候set(初始化的时候) // //测试用例指定要获取的beanClassName // Object bean = null;//beanDefinition.getBeanClass().newInstance() // try { // bean = doCreateBean(beanDefinition); // beanDefinition.setBean(bean); // beanDefinitionMap.put(name, beanDefinition); // } catch (Exception e) { // logger.error(e.getMessage(),e); // } // // } abstract Object createBean(BeanDefinition beanDefinition); abstract Object createBeanInstance(BeanDefinition beanDefinition); abstract void populateBean(Object instance, BeanDefinition beanDefinition); abstract void initializeBean(Object instance, BeanDefinition beanDefinition); } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/factory/support/BeanDefinitionReader.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.factory.support; /** * 读取器,从XML或者注解 * * @author Allen Jiang * @since 1.0.0 */ public interface BeanDefinitionReader { //void analysisResourceList(String location) throws IOException; } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/factory/support/DefaultListableBeanFactory.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.factory.support; import io.gamioo.ioc.annotation.CommandMapping; import io.gamioo.ioc.annotation.RequestMapping; import io.gamioo.ioc.definition.BeanDefinition; import io.gamioo.ioc.definition.MethodDefinition; import io.gamioo.ioc.stereotype.Controller; import io.gamioo.ioc.wrapper.Command; import io.gamioo.ioc.wrapper.MethodWrapper; import java.lang.annotation.Annotation; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory { /** * 控制器的消息处理容器 */ protected final Map commandStore = new ConcurrentHashMap<>(1024); } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/factory/xml/XmlBeanDefinitionReader.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.factory.xml; import io.gamioo.ioc.PropertyValue; import io.gamioo.ioc.config.BeanReference; import io.gamioo.ioc.factory.support.AbstractBeanDefinition; import io.gamioo.ioc.factory.support.AbstractBeanDefinitionReader; import io.gamioo.ioc.definition.GenericBeanDefinition; import io.gamioo.ioc.io.Resource; import io.gamioo.ioc.io.ResourceLoader; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import java.io.IOException; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { public XmlBeanDefinitionReader(ResourceLoader resourceLoader) { super(null); } public void analysisResourceList(String location) throws IOException { // List list = this.getResourceLoader().getResourceList(location); // for (Resource e : list) { // analysisResource(e); // } } protected void analysisResource(Resource resource) throws IOException { try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder docBuilder = factory.newDocumentBuilder(); Document doc = docBuilder.parse(resource.getName()); // 解析bean registerBeanDefinitions(doc); // resource.getInputStream().close(); } catch (Exception e) { } } public void registerBeanDefinitions(Document doc) { Element root = doc.getDocumentElement(); parseBeanDefinitions(root); } protected void parseBeanDefinitions(Element root) { NodeList nl = root.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; processBeanDefinition(ele); } } } protected void processBeanDefinition(Element ele) { String name = ele.getAttribute("id"); String className = ele.getAttribute("class"); GenericBeanDefinition beanDefinition = new GenericBeanDefinition(null,null); /// beanDefinition.setBeanClassName(className); this.getBeanFactory().registerBeanDefinition(name, beanDefinition); // getRegistry().put(name, beanDefinition); } private void processProperty(Element ele, AbstractBeanDefinition beanDefinition) { NodeList propertyNode = ele.getElementsByTagName("property"); for (int i = 0; i < propertyNode.getLength(); i++) { Node node = propertyNode.item(i); if (node instanceof Element) { Element propertyEle = (Element) node; String name = propertyEle.getAttribute("name"); String value = propertyEle.getAttribute("value"); if (value != null && value.length() > 0) { beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, value)); } else { String ref = propertyEle.getAttribute("ref"); if (ref == null || ref.length() == 0) { throw new IllegalArgumentException("Configuration problem: element for property '" + name + "' must specify a ref or value"); } BeanReference beanReference = new BeanReference(ref); beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, beanReference)); } } } } } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/factory/xml/XmlResourceLoader.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.factory.xml; import io.gamioo.ioc.io.Resource; import io.gamioo.ioc.io.ResourceLoader; import io.gamioo.ioc.io.UrlFileResource; import java.io.IOException; import java.util.ArrayList; import java.util.List; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public class XmlResourceLoader implements ResourceLoader { /** * Resolve the given location pattern into Resource objects. *

Overlapping resource entries that point to the same physical * resource should be avoided, as far as possible. The result should * have set semantics. * * @param location the location pattern to resolve * @return the corresponding Resource objects * @throws IOException in case of I/O errors */ @Override public List getResourceList(String location) throws IOException { List ret=new ArrayList<>(); // URL url = ClassUtils.getDefaultClassLoader().getResource(location); UrlFileResource resource=new UrlFileResource(location); ret.add(resource); return ret; } } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/io/AbstractResource.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.io; import org.apache.commons.lang3.NotImplementedException; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public abstract class AbstractResource implements Resource{ protected final String name; protected AbstractResource(String name) { this.name = name; } @Override public String getName() { return name; } @Override public String getClassName() { throw new NotImplementedException(this.getClass().getName()); } @Override public String toString() { return "AbstractResource [name=" + name + "]"; } } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/io/FileClassResource.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.io; import java.io.File; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public class FileClassResource extends AbstractResource { private final File file; public FileClassResource(String path, File file) { super(path + file.getName()); this.file=file; } @Override public String getClassName() { // io/gamioo/ioc/AnnotationBeanFactoryTest.class return name.substring(0, name.length() - 6).replace("/","."); } } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/io/JarClassResource.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.io; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public class JarClassResource extends AbstractResource { public JarClassResource(String path) { super(path); } @Override public String getClassName() { // io/gamioo/ioc/AnnotationBeanFactoryTest.class return name.substring(0, name.length() - 6).replace("/","."); } } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/io/Resource.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.io; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public interface Resource { /** * 获得资源的名称. * * @return 资源的名称 */ String getName(); /***/ String getClassName(); } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/io/ResourceCallback.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.io; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public interface ResourceCallback { void handle(); } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/io/ResourceLoader.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.io; import java.io.IOException; import java.util.List; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public interface ResourceLoader { // public String getLocation(); List getResourceList(String location) throws IOException; // Resource getResource(String location); } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/io/UrlFileResource.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.io; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public class UrlFileResource extends AbstractResource { public UrlFileResource(String uri) { super(uri); } } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/stereotype/Component.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.stereotype; import java.lang.annotation.*; /** * some description * * @author Allen Jiang * @since 1.0.0 */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Component { /** * 这个类的唯一名称. *

主要用于Map注入的KEY

* * @return 名称 */ String[] name() default {}; } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/stereotype/Controller.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.stereotype; import io.gamioo.common.concurrent.Group; import java.lang.annotation.*; /** * some description * * @author Allen Jiang * @since 1.0.0 */ @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Controller { /**模块*/ String module() default ""; /** * 标识这个协议控制中的入口方法由哪个线程组调用. *

* 默认转化为串型执行队列线程 * * @return 执行线程组. */ Group group() default Group.QueueThreadGroup; } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/stereotype/Repository.java ================================================ package io.gamioo.ioc.stereotype; import java.lang.annotation.*; /** * Repository注解用来标识一个数据库处理类. * * @author Allen Jiang * @since 1.0.0 */ @Component @Documented @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Repository { } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/stereotype/Resource.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.stereotype; import java.lang.annotation.*; /** * 资源管理类 * * @author Allen Jiang * @since 1.0.0 */ @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Resource { String value() default ""; } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/stereotype/Service.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.stereotype; import java.lang.annotation.*; /** * some description * * @author Allen Jiang * @since 1.0.0 */ @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Service { String value() default ""; } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/stereotype/package-info.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * 一些关键的注解 * * @author Allen Jiang * @since 1.0.0 */ package io.gamioo.ioc.stereotype; ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/type/AnnotatedTypeMetadata.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.type; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public interface AnnotatedTypeMetadata { /** * Determine whether the underlying element has an annotation or meta-annotation * of the given type defined. *

If this method returns {@code true}, then * @param annotationName the fully qualified class name of the annotation * type to look for * @return whether a matching annotation is defined */ boolean isAnnotated(String annotationName); } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/type/AnnotationMetadata.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.type; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public interface AnnotationMetadata extends ClassMetadata, AnnotatedTypeMetadata{ } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/type/ClassMetadata.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.type; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public interface ClassMetadata { /** * Return the name of the underlying class. */ String getClassName(); /** * Return whether the underlying class represents an interface. */ boolean isInterface(); /** * Return whether the underlying class represents an annotation. * @since 4.1 */ boolean isAnnotation(); /** * Return whether the underlying class is marked as abstract. */ boolean isAbstract(); /** * Return whether the underlying class represents a concrete class, * i.e. neither an interface nor an abstract class. */ boolean isConcrete(); /** * Return whether the underlying class is marked as 'final'. */ boolean isFinal(); /** * Determine whether the underlying class is independent, * i.e. whether it is a top-level class or a nested class * (static inner class) that can be constructed independent * from an enclosing class. */ boolean isIndependent(); /** * Return whether the underlying class has an enclosing class * (i.e. the underlying class is an inner/nested class or * a local class within a method). *

If this method returns {@code false}, then the * underlying class is a top-level class. */ boolean hasEnclosingClass(); /** * Return the name of the enclosing class of the underlying class, * or {@code null} if the underlying class is a top-level class. */ String getEnclosingClassName(); /** * Return whether the underlying class has a super class. */ boolean hasSuperClass(); /** * Return the name of the super class of the underlying class, * or {@code null} if there is no super class defined. */ String getSuperClassName(); } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/type/MethodMetadata.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.type; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public interface MethodMetadata extends AnnotatedTypeMetadata { /** * Return the name of the method. */ String getMethodName(); /** * Return the fully-qualified name of the class that declares this method. */ String getDeclaringClassName(); /** * Return the fully-qualified name of this method's declared return type. * */ String getReturnTypeName(); /** * Return whether the underlying method is effectively abstract: * i.e. marked as abstract on a class or declared as a regular, * non-default method in an interface. * * @since 4.2 */ boolean isAbstract(); /** * Return whether the underlying method is declared as 'static'. */ boolean isStatic(); /** * Return whether the underlying method is marked as 'final'. */ boolean isFinal(); /** * Return whether the underlying method is overridable, * i.e. not marked as static, final or private. */ boolean isOverridable(); } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/type/classreading/AnnotationMetadataReadingVisitor.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.type.classreading; import io.gamioo.ioc.type.AnnotationMetadata; import io.gamioo.ioc.type.MethodMetadata; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor implements AnnotationMetadata { protected final ClassLoader classLoader; protected final Set annotationSet = new LinkedHashSet(4); protected final Map> metaAnnotationMap = new LinkedHashMap>(4); protected final Set methodMetadataSet = new LinkedHashSet(4); public AnnotationMetadataReadingVisitor(ClassLoader classLoader) { this.classLoader = classLoader; } public Set getAnnotationTypes() { return this.annotationSet; } public Set getMetaAnnotationTypes(String annotationName) { return this.metaAnnotationMap.get(annotationName); } public boolean hasAnnotation(String annotationName) { return this.annotationSet.contains(annotationName); } @Override public boolean isAnnotated(String annotationName) { return true; } } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/type/classreading/ClassMetadataReadingVisitor.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.type.classreading; import io.gamioo.ioc.type.ClassMetadata; import java.util.LinkedHashSet; import java.util.Set; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public class ClassMetadataReadingVisitor implements ClassMetadata { private String className; private boolean isInterface; private boolean isAnnotation; private boolean isAbstract; private boolean isFinal; private String enclosingClassName; private boolean independentInnerClass; private String superClassName; private String[] interfaces; private Set memberClassNames = new LinkedHashSet(); public String getClassName() { return className; } public boolean isInterface() { return isInterface; } public boolean isAnnotation() { return isAnnotation; } public boolean isAbstract() { return isAbstract; } public boolean isFinal() { return isFinal; } public String getEnclosingClassName() { return enclosingClassName; } public boolean isIndependentInnerClass() { return independentInnerClass; } public String getSuperClassName() { return superClassName; } public String[] getInterfaces() { return interfaces; } public Set getMemberClassNames() { return memberClassNames; } @Override public boolean isConcrete() { return !(this.isInterface || this.isAbstract); } @Override public boolean isIndependent() { return (this.enclosingClassName == null || this.independentInnerClass); } @Override public boolean hasEnclosingClass() { return (this.enclosingClassName != null); } @Override public boolean hasSuperClass() { return (this.superClassName != null); } } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/type/classreading/MetadataReader.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.type.classreading; import io.gamioo.ioc.io.Resource; import io.gamioo.ioc.type.AnnotationMetadata; import io.gamioo.ioc.type.ClassMetadata; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public interface MetadataReader { /** * Return the resource reference for the class file. */ Resource getResource(); /** * Read basic class metadata for the underlying class. */ ClassMetadata getClassMetadata(); /** * Read full annotation metadata for the underlying class, * including metadata for annotated methods. */ AnnotationMetadata getAnnotationMetadata(); } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/wrapper/BeanWrapper.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.wrapper; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public class BeanWrapper { private Object object; public BeanWrapper(Object object) { this.object = object; } /** * Return the bean instance wrapped by this object, if any. * * @return the bean instance, or {@code null} if none set */ public Object getWrappedInstance() { return this.object; } /** * Return the type of the wrapped JavaBean object. * * @return the type of the wrapped bean instance, * or {@code null} if no wrapped object has been set */ public Class getWrappedClass() { return (this.object != null ? this.object.getClass() : null); } } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/wrapper/Command.java ================================================ package io.gamioo.ioc.wrapper; import io.gamioo.ioc.annotation.CommandMapping; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.util.concurrent.atomic.LongAdder; /** * 指令包装类 * * @author Allen Jiang * @since 1.0.0 */ public class Command { private static final Logger logger = LogManager.getLogger(Command.class); private MethodWrapper wrapper; private final int code; private boolean valid; private final boolean print; private final boolean login; private final boolean cross; /** * 调用次数累加 */ private final LongAdder number = new LongAdder(); public Command(MethodWrapper wrapper, CommandMapping mapping) { this.wrapper = wrapper; this.code = mapping.code(); this.print = mapping.print(); this.login = mapping.login(); this.cross = mapping.cross(); } public int getCode() { return code; } public boolean isValid() { return valid; } public void setValid(boolean valid) { this.valid = valid; } public boolean isPrint() { return print; } public boolean isLogin() { return login; } public boolean isCross() { return cross; } public void increase() { number.increment(); } public long getInvokeNumber() { return number.longValue(); } public String toString() { return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); } } ================================================ FILE: gamioo-ioc/src/main/java/io/gamioo/ioc/wrapper/MethodWrapper.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.wrapper; import com.esotericsoftware.reflectasm.MethodAccess; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import java.lang.reflect.Method; /** * 方法的包装类 * * @author Allen Jiang * @since 1.0.0 */ public class MethodWrapper { private MethodAccess access; private int index;// 方法索引 private Object instance; private Class paramClazz;// 参数类型 private String name; public MethodWrapper( Object instance,Method method,MethodAccess access,int index) { this.name = method.getName(); this.access = access; this.index = index; this.instance = instance; this.paramClazz = method.getParameterTypes()[0]; } public MethodWrapper(Method method, Object instance) { this.name = method.getName(); this.access = MethodAccess.get(instance.getClass()); this.index = access.getIndex(method.getName(), method.getParameterTypes()); this.instance = instance; this.paramClazz = method.getParameterTypes()[0]; } public Object invoke(Object... args) { Object ret = null; if (args == null) { // 无参数调用 ret = access.invoke(instance, index); } else { // 有参数调用 ret = access.invoke(instance, index, args); } return ret; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Class getParamClazz() { return paramClazz; } public void setParamClazz(Class paramClazz) { this.paramClazz = paramClazz; } public String toString() { return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); } } ================================================ FILE: gamioo-ioc/src/test/java/io/gamioo/ioc/DebugService.java ================================================ package io.gamioo.ioc; import io.gamioo.ioc.factory.annotation.Autowired; import io.gamioo.ioc.map.Command; import io.gamioo.ioc.stereotype.Service; import java.util.Map; /** * some description * * @author Allen Jiang * @since 1.0.0 */ @Service public class DebugService { @Autowired private Map commandStore; public void handle(String value){ Command command=commandStore.get(value); if(command!=null){ command.execute(); } } } ================================================ FILE: gamioo-ioc/src/test/java/io/gamioo/ioc/IOCFactoryTest.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc; import io.gamioo.ioc.context.ConfigApplicationContext; import io.gamioo.ioc.entity.ServerConfig; import io.gamioo.ioc.skill.AbstractSkill; import io.gamioo.ioc.wrapper.Command; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.junit.jupiter.api.*; import java.util.List; /** * some description * * @author Allen Jiang * @since 1.0.0 */ @DisplayName("IOC测试") @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class IOCFactoryTest { private static final Logger logger = LogManager.getLogger(IOCFactoryTest.class); //private final Benchmark=new Benchmark(10000); private static ConfigApplicationContext context; @BeforeAll public static void beforeAll() { //初始化...... context = new ConfigApplicationContext(IOCFactoryTest.class.getPackage().getName()); } @Test @Order(1) @DisplayName("容器扫描和获取") public void test() { // //初始化完毕,获取想要的bean RoleService roleService = context.getBean(RoleService.class); roleService.handleCommand("-- add rmb"); List list = roleService.getSkillList(); logger.debug("array={}", list); } @Test @Order(2) @DisplayName("配置文件读取") public void handleConfig() { ServerConfig serverConfig = context.getBean(ServerConfig.class); logger.debug("serverConfig={}", serverConfig); } @Test @Order(3) @DisplayName("指令读取") public void handleControl() { Command command = context.getCommand(10001); logger.debug("command={}", command); } @BeforeEach public void beforeEach() { } @AfterEach public void afterEach() { } @AfterAll public static void afterAll() { } } ================================================ FILE: gamioo-ioc/src/test/java/io/gamioo/ioc/MapTest.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc; import java.util.HashMap; import java.util.Map; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public class MapTest { public static void main(String[] args) { Map map=new HashMap<>(); Object obj= map.computeIfAbsent("a",key->new Object()); System.out.println(obj); Object d=map.putIfAbsent("d",new Object()); System.out.println(d); map.computeIfAbsent("a",key->new Object()); System.out.println(map); } } ================================================ FILE: gamioo-ioc/src/test/java/io/gamioo/ioc/RoleService.java ================================================ package io.gamioo.ioc; import io.gamioo.ioc.annotation.Subscribe; import io.gamioo.ioc.factory.annotation.Autowired; import io.gamioo.ioc.skill.AbstractSkill; import io.gamioo.ioc.stereotype.Service; import java.util.List; /** * some description * * @author Allen Jiang * @since 1.0.0 */ @Service public class RoleService { @Autowired private SkillService skillService; @Autowired private DebugService debugService; public void handleCommand(String value) { debugService.handle(value); } public List getSkillList() { return skillService.getSkillList(); } } ================================================ FILE: gamioo-ioc/src/test/java/io/gamioo/ioc/SkillService.java ================================================ package io.gamioo.ioc; import io.gamioo.ioc.factory.annotation.Autowired; import io.gamioo.ioc.skill.AbstractSkill; import io.gamioo.ioc.stereotype.Service; import java.util.List; /** * some description * * @author Allen Jiang * @since 1.0.0 */ @Service public class SkillService { @Autowired private List skillList; public List getSkillList() { return skillList; } } ================================================ FILE: gamioo-ioc/src/test/java/io/gamioo/ioc/XmlBeanFactoryTest.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc; import io.gamioo.ioc.factory.support.DefaultListableBeanFactory; import io.gamioo.ioc.factory.xml.XmlBeanDefinitionReader; import io.gamioo.ioc.factory.xml.XmlResourceLoader; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.junit.jupiter.api.*; /** * some description * * @author Allen Jiang * @since 1.0.0 */ @DisplayName("IOC测试") @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class XmlBeanFactoryTest { private static final Logger logger = LogManager.getLogger(XmlBeanFactoryTest.class); //private final Benchmark benchmark=new Benchmark(10000); private static XmlBeanDefinitionReader xmlBeanDefinitionReader; @BeforeAll public static void beforeAll() throws Exception { //初始化...... xmlBeanDefinitionReader = new XmlBeanDefinitionReader(new XmlResourceLoader()); xmlBeanDefinitionReader.analysisResourceList("ioc.xml"); } @Test @Order(1) @DisplayName("IOC2测试") public void test() throws Exception { DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); // for (Map.Entry beanDefinitionEntry : beanFactory.entrySet()) { // beanFactory.registerBeanDefinition(beanDefinitionEntry.getKey(), beanDefinitionEntry.getValue()); // } } @BeforeEach public void beforeEach() { } @AfterEach public void afterEach() { } @AfterAll public static void afterAll() { } } ================================================ FILE: gamioo-ioc/src/test/java/io/gamioo/ioc/action/UserController.java ================================================ package io.gamioo.ioc.action; import io.gamioo.ioc.annotation.CommandMapping; import io.gamioo.ioc.stereotype.Controller; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; /** * some description * * @author Allen Jiang * @since 1.0.0 */ @Controller public class UserController { private static final Logger logger = LogManager.getLogger(UserController.class); @CommandMapping(code=10001,cross = true,print = false,login = false) public void login(Object session,String message){ logger.debug("object={},message={}",session,message); } } ================================================ FILE: gamioo-ioc/src/test/java/io/gamioo/ioc/entity/Cache.java ================================================ package io.gamioo.ioc.entity; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public class Cache { private int type; private String ip; private int port; private int index; private String password; public int getType() { return type; } public void setType(int type) { this.type = type; } public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } public int getPort() { return port; } public void setPort(int port) { this.port = port; } public int getIndex() { return index; } public void setIndex(int index) { this.index = index; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } } ================================================ FILE: gamioo-ioc/src/test/java/io/gamioo/ioc/entity/DB.java ================================================ package io.gamioo.ioc.entity; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public class DB { private String jdbcUser; private String jdbcUrl; private String jdbcPassword; public String getJdbcUser() { return jdbcUser; } public void setJdbcUser(String jdbcUser) { this.jdbcUser = jdbcUser; } public String getJdbcUrl() { return jdbcUrl; } public void setJdbcUrl(String jdbcUrl) { this.jdbcUrl = jdbcUrl; } public String getJdbcPassword() { return jdbcPassword; } public void setJdbcPassword(String jdbcPassword) { this.jdbcPassword = jdbcPassword; } } ================================================ FILE: gamioo-ioc/src/test/java/io/gamioo/ioc/entity/Server.java ================================================ package io.gamioo.ioc.entity; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public class Server { private int tcpPort; private String name; private int id; private String externalIp; private int webPort; public int getTcpPort() { return tcpPort; } public void setTcpPort(int tcpPort) { this.tcpPort = tcpPort; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getExternalIp() { return externalIp; } public void setExternalIp(String externalIp) { this.externalIp = externalIp; } public int getWebPort() { return webPort; } public void setWebPort(int webPort) { this.webPort = webPort; } } ================================================ FILE: gamioo-ioc/src/test/java/io/gamioo/ioc/entity/ServerConfig.java ================================================ /* * Copyright 2015-2020 Gamioo Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.ioc.entity; import io.gamioo.ioc.annotation.Configuration; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import java.util.List; /** * some description * * @author Allen Jiang * @since 1.0.0 */ @Configuration("gate-config.xml") public class ServerConfig { private boolean location; private String local; private boolean debug; private int offlineInterval; private int saveInterval; private int rpcTimeout; private DB db; private List cache; private Server server; public boolean isLocation() { return location; } public void setLocation(boolean location) { this.location = location; } public String getLocal() { return local; } public void setLocal(String local) { this.local = local; } public boolean isDebug() { return debug; } public void setDebug(boolean debug) { this.debug = debug; } public int getOfflineInterval() { return offlineInterval; } public void setOfflineInterval(int offlineInterval) { this.offlineInterval = offlineInterval; } public int getSaveInterval() { return saveInterval; } public void setSaveInterval(int saveInterval) { this.saveInterval = saveInterval; } public int getRpcTimeout() { return rpcTimeout; } public void setRpcTimeout(int rpcTimeout) { this.rpcTimeout = rpcTimeout; } public DB getDb() { return db; } public void setDb(DB db) { this.db = db; } public List getCache() { return cache; } public void setCache(List cache) { this.cache = cache; } public Server getServer() { return server; } public void setServer(Server server) { this.server = server; } public String toString() { return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); } } ================================================ FILE: gamioo-ioc/src/test/java/io/gamioo/ioc/map/AddItemCommand.java ================================================ package io.gamioo.ioc.map; import io.gamioo.ioc.stereotype.Component; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; /** * some description * * @author Allen Jiang * @since 1.0.0 */ @Component(name="-- add item") public class AddItemCommand implements Command { private static final Logger logger = LogManager.getLogger(AddItemCommand.class); @Override public void execute() { logger.debug("{} execute",this.getClass().getSimpleName()); } } ================================================ FILE: gamioo-ioc/src/test/java/io/gamioo/ioc/map/AddMoneyCommand.java ================================================ package io.gamioo.ioc.map; import io.gamioo.ioc.stereotype.Component; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; /** * some description * * @author Allen Jiang * @since 1.0.0 */ @Component(name="-- add money") public class AddMoneyCommand implements Command { private static final Logger logger = LogManager.getLogger(AddMoneyCommand.class); @Override public void execute() { logger.debug("{} execute",this.getClass().getSimpleName()); } } ================================================ FILE: gamioo-ioc/src/test/java/io/gamioo/ioc/map/AddRmbCommand.java ================================================ package io.gamioo.ioc.map; import io.gamioo.ioc.stereotype.Component; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; /** * some description * * @author Allen Jiang * @since 1.0.0 */ @Component(name="-- add rmb") public class AddRmbCommand implements Command { private static final Logger logger = LogManager.getLogger(AddRmbCommand.class); @Override public void execute() { logger.debug("{} execute",this.getClass().getSimpleName()); } } ================================================ FILE: gamioo-ioc/src/test/java/io/gamioo/ioc/map/Command.java ================================================ package io.gamioo.ioc.map; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public interface Command { void execute(); } ================================================ FILE: gamioo-ioc/src/test/java/io/gamioo/ioc/skill/AbstractSkill.java ================================================ package io.gamioo.ioc.skill; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; /** * some description * * @author Allen Jiang * @since 1.0.0 */ public abstract class AbstractSkill { public abstract void init(); public abstract void handle(); public String toString(){ return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); } } ================================================ FILE: gamioo-ioc/src/test/java/io/gamioo/ioc/skill/SkillA.java ================================================ package io.gamioo.ioc.skill; import io.gamioo.ioc.stereotype.Component; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import javax.annotation.PostConstruct; /** * some description * * @author Allen Jiang * @since 1.0.0 */ @Component(name="A") public class SkillA extends AbstractSkill{ private static final Logger logger = LogManager.getLogger(SkillA.class); @PostConstruct public void init() { logger.debug("{} init",this.getClass().getSimpleName()); } @Override public void handle() { logger.debug("{} handle",this.getClass().getSimpleName()); } } ================================================ FILE: gamioo-ioc/src/test/java/io/gamioo/ioc/skill/SkillB.java ================================================ package io.gamioo.ioc.skill; import io.gamioo.ioc.stereotype.Component; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import javax.annotation.PostConstruct; /** * some description * * @author Allen Jiang * @since 1.0.0 */ @Component(name="B") public class SkillB extends AbstractSkill{ private static final Logger logger = LogManager.getLogger(SkillB.class); @Override @PostConstruct public void init() { logger.debug("{} init",this.getClass().getSimpleName()); } @Override public void handle() { logger.debug("{} handle",this.getClass().getSimpleName()); } } ================================================ FILE: gamioo-ioc/src/test/resources/gate-config.xml ================================================ ================================================ FILE: gamioo-ioc/src/test/resources/ioc.xml ================================================ ================================================ FILE: gamioo-ioc/src/test/resources/junit-platform.properties ================================================ junit.jupiter.execution.parallel.enabled=true #\u7c7b\u5185\u90e8\u65b9\u6cd5\u5e76\u884c junit.jupiter.execution.parallel.mode.default = concurrent #\u7c7b\u4e4b\u95f4\u4e32\u884c junit.jupiter.execution.parallel.mode.classes.default = same_thread # the maximum pool size can be configured using a ParallelExecutionConfigurationStrategy junit.jupiter.execution.parallel.config.strategy=fixed junit.jupiter.execution.parallel.config.fixed.parallelism=8 ================================================ FILE: gamioo-ioc/src/test/resources/log4j2.component.properties ================================================ Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector # default values is 256*1024 AsyncLogger.RingBufferSize=131072 ================================================ FILE: gamioo-ioc/src/test/resources/log4j2.xml ================================================ 1 benchmark /data/log/${SERVER_NAME}/${SERVER_ID} /data/stat/gamioo/${SERVER_NAME}/${SERVER_ID} ================================================ FILE: gamioo-log/.settings/.gitignore ================================================ /org.eclipse.buildship.core.prefs ================================================ FILE: gamioo-log/build.gradle ================================================ dependencies { api group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.17.1'; api group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.17.1'; api group: 'org.apache.logging.log4j', name: 'log4j-web', version: '2.17.1'; //桥接:告诉Slf4j使用Log4j2 api group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: '2.17.1'; //桥接:告诉commons logging使用 Log4j2 api group: 'org.apache.logging.log4j', name: 'log4j-jcl', version: '2.17.1'; api group: 'org.slf4j', name: 'slf4j-api', version: '1.7.30'; // api group: 'org.slf4j', name: 'slf4j-log4j12', version: '1.7.30'; api group: 'com.lmax', name: 'disruptor', version: '3.4.2'; } ================================================ FILE: gamioo-log/src/main/java/io/gamioo/log/MainT.java ================================================ package io.gamioo.log; public class MainT { public static void main(String[] args) { System.out.println("hello ketty"); } } ================================================ FILE: gamioo-log/src/main/java/io/gamioo/log/package-info.java ================================================ /** * 日志库. */ package io.gamioo.log; ================================================ FILE: gamioo-log/src/main/resources/log4j2.component.properties ================================================ Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector # default values is 256*1024 AsyncLogger.RingBufferSize=131072 isThreadContextMapInheritable=false ================================================ FILE: gamioo-log/src/main/resources/log4j2.xml ================================================ ================================================ FILE: gamioo-log/src/test/java/io/gamioo/log/Main.java ================================================ package io.gamioo.log; public class Main { public static void main(String[] args) { System.out.println(); } } ================================================ FILE: gamioo-log/src/test/resources/log4j2.xml ================================================ ================================================ FILE: gamioo-navigation/README.md ================================================

Game , so easy.

github star

# 简介 📌 研究寻路导航相关 * 寻路算法 * NavMesh * A*

```java class Main { public static void main(String[] args) { NavEngine nav = new NavEngine(); nav.init(1, "solo_navmesh.bin"); float[] src = new float[]{-49.7f, 0.2f, 135.5f}; float[] end = new float[]{-106.3f, 0.5f, 77.2f}; List array = nav.find(src, end); } } ``` #### 📄寻路结果如下: ``` [[-49.7, 0.4, 135.5], [-66.79999, 1.0, 135.6], [-69.2, 0.8, 134.70001], [-70.7, 0.6, 132.6] , [-73.99999, 0.2, 127.8], [-106.3, 0.78799236, 77.2]] ``` #### 📄 性能测试结果如下: 在Windows 11 下(4核8线程 Intel(R) Core(TM)i7-10510U CPU @ 1.80GHz) ```bash Benchmark Mode Cnt Score Error Units NavEngineBenchMark.javaFind thrpt 10 28605.626 ± 1331.775 ops/s NavEngineBenchMark.javaFindNearest thrpt 10 497177.122 ± 12048.418 ops/s NavEngineBenchMark.javaRaycast thrpt 10 401200.127 ± 7926.908 ops/s NavEngineBenchMark.nativeFind thrpt 10 82924.746 ± 1942.112 ops/s NavEngineBenchMark.nativeFindNearest thrpt 10 1438972.883 ± 28769.610 ops/s NavEngineBenchMark.nativeRaycast thrpt 10 1096445.690 ± 29597.755 ops/s ``` - 寻路API,性能达到了原先的289.89%; - 寻找最近可通点API,性能达到了原先的289.43%; - 光线照反射API,性能达到了原先的273.29%; 在CentOS Linux 7 (8核16线程 Intel(R) Xeon(R) Platinum 8372C CPU model 106 @ 3.20GHz) ```bash Benchmark Mode Cnt Score Error Units NavEngineBenchMark.javaFind thrpt 10 38372.243 ± 80.293 ops/s NavEngineBenchMark.javaFindNearest thrpt 10 518859.216 ± 767.435 ops/s NavEngineBenchMark.javaRaycast thrpt 10 442360.257 ± 287.334 ops/s NavEngineBenchMark.nativeFind thrpt 10 26796.756 ± 49.669 ops/s NavEngineBenchMark.nativeFindNearest thrpt 10 393484.307 ± 844.553 ops/s NavEngineBenchMark.nativeRaycast thrpt 10 305434.422 ± 851.248 ops/s ``` - 寻路API,性能降到了原先的69.83%; - 寻找最近可通点API,性能降到了原先的 75.84%; - 光线照反射API,性能降到了原先的69.05%; ### 依赖&参考 https://github.com/SilenceSu/Easy3dNav ## TODO list * 寻路算法 * A* ================================================ FILE: gamioo-navigation/build.gradle ================================================ dependencies { implementation project(':gamioo-common'); // implementation 'com.github.silencesu:Easy3dNav:1.1.0'; implementation('com.github.silencesu:Easy3dNav:1.1.0') { exclude group: "ch.qos.logback", module: "logback-classic" } } ================================================ FILE: gamioo-navigation/src/jmh/java/io/gamioo/nav/NavEngineBenchMark.java ================================================ package io.gamioo.nav; import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import java.io.IOException; import java.util.List; import java.util.concurrent.TimeUnit; /** * @author Allen Jiang */ @State(Scope.Benchmark) @BenchmarkMode({Mode.Throughput}) @OutputTimeUnit(TimeUnit.SECONDS) @Warmup(iterations = 5, time = 1) @Measurement(iterations = 5, time = 1) @Fork(value = 1) public class NavEngineBenchMark { /** * Benchmark Mode Cnt Score Error Units * Nav3DBenchMark.find thrpt 10 248280.038 ± 1460.364 ops/s * Nav3DBenchMark.raycast thrpt 10 816733.804 ± 9337.084 ops/s */ private NavEngine nav; public static int id; float[] src = new float[]{-112.082f, 0.359222f, 55.5665f}; float[] end = new float[]{-49.0154f, 0.0922241f, 104.259f}; private float[] extents = {1.f, 1.f, 1.f}; float[] target = new float[]{-86.6f, 1.8f, 53.6f}; private String navFilePath = "solo_navmesh.bin"; private Easy3dNav easyNav = new Easy3dNav(); @Setup(Level.Trial) public void init() throws IOException { nav = new NavEngine(); id = 1; nav.init(id, navFilePath); //初始化寻路对象 easyNav = new Easy3dNav(); //默认为true,可以忽略 easyNav.setUseU3dData(false); //默认为false,查看需要设置为true easyNav.setPrintMeshInfo(false); easyNav.setExtents(extents); easyNav.init(navFilePath); } @Benchmark public List nativeFind() { return nav.find(src, end); } @Benchmark public float[] nativeRaycast() { return nav.raycast(src, end); } @Benchmark public float[] nativeFindNearest() { return nav.findNearest(target); } @Benchmark public List javaFind() { return easyNav.find(src, end); } @Benchmark public float[] javaRaycast() { return easyNav.raycast(src, end); } @Benchmark public float[] javaFindNearest() { return easyNav.findNearest(target); } public static void main(String[] args) { Options opt = new OptionsBuilder() .include(NavEngineBenchMark.class.getSimpleName()).build(); try { new Runner(opt).run(); } catch (RunnerException e) { e.printStackTrace(); } } } ================================================ FILE: gamioo-navigation/src/main/java/io/gamioo/nav/Easy3dNav.java ================================================ package io.gamioo.nav; import com.github.silencesu.Easy3dNav.EasyNavFunc; import com.github.silencesu.Easy3dNav.Vector3f; import com.github.silencesu.Easy3dNav.detour.*; import com.github.silencesu.Easy3dNav.detour.io.MeshSetReader; import com.github.silencesu.Easy3dNav.detour.io.MeshSetReaderU3d; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; /** * 3d游戏服务端寻路组件 * * @author Allen Jiang */ public class Easy3dNav implements EasyNavFunc { private static Logger LOGGER = LoggerFactory.getLogger(com.github.silencesu.Easy3dNav.Easy3dNav.class); /** * 是否使用U3d插件CritterAI导出的格式 */ private boolean useU3dData = true; /** * 是否打印地图信息 */ private boolean printMeshInfo = false; private NavMeshQuery query; private QueryFilter filter; private float[] extents = {2.f, 2.f, 2.f}; public Easy3dNav() { } /** * 修改配置 构造 * * @param useU3dData 是否使用critterAi数据 * @param printMeshInfo 是否打印地图西信息 */ public Easy3dNav(boolean useU3dData, boolean printMeshInfo) { this.useU3dData = useU3dData; this.printMeshInfo = printMeshInfo; } //navmesh 文件路径 public Easy3dNav(String filePath) throws IOException { init(filePath); } /** * 初始化寻路需要参数 * * @param filePath */ public void init(String filePath) throws IOException { NavMesh mesh = loadNavMesh(filePath); query = new NavMeshQuery(mesh); filter = new QueryFilter(); if (printMeshInfo) { printMeshInfo(mesh); } } private NavMesh loadNavMesh(String meshFile) throws IOException { InputStream inputStream = null; NavMesh mesh; try { //获取文件流 inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(meshFile); if (useU3dData) { MeshSetReaderU3d reader = new MeshSetReaderU3d(); mesh = reader.read32Bit(inputStream, 6); } else { MeshSetReader reader = new MeshSetReader(); mesh = reader.read32Bit(inputStream, 6); } query = new NavMeshQuery(mesh); filter = new QueryFilter(); } finally { //使用完,关闭流 try { if (inputStream != null) { inputStream.close(); } } catch (IOException e) { e.printStackTrace(); } } return mesh; } /** * 输出mesh信息 * * @param mesh meshd对象 */ private void printMeshInfo(NavMesh mesh) { //输出地图基本信息 int tileCount = 0; int nodeCount = 0; int polyCount = 0; int vertCount = 0; int triCount = 0; int triVertCount = 0; int dataSize = 0; for (int i = 0; i < mesh.getMaxTiles(); i++) { MeshTile tile = mesh.getTile(i); if (tile == null) { continue; } tileCount++; nodeCount += tile.data.header.bvNodeCount; polyCount += tile.data.header.polyCount; vertCount += tile.data.header.vertCount; triCount += tile.data.header.detailTriCount; triVertCount += tile.data.header.detailVertCount; System.out.printf("%f %f %f\n", tile.data.verts[0], tile.data.verts[1], tile.data.verts[2]); for (int m = 0; m < tile.data.header.detailVertCount; m++) { System.out.printf("%f %f %f\n", tile.data.detailVerts[m * 3], tile.data.detailVerts[m * 3 + 1], tile.data.detailVerts[m * 3 + 2]); } } System.out.printf("\t==> tiles loaded: %d\n", tileCount); System.out.printf("\t==> BVTree nodes: %d\n", nodeCount); System.out.printf("\t==> %d polygons (%d vertices)\n", polyCount, vertCount); System.out.printf("\t==> %d triangles (%d vertices)\n", triCount, triVertCount); } /** * 设置默认搜索范围 * * @param extents 搜索范围 */ public void setExtents(float[] extents) { this.extents = extents; } /** * 设置是否使用critterAI导出的数据 * * @param useU3dData * */ public void setUseU3dData(boolean useU3dData) { this.useU3dData = useU3dData; } public void setPrintMeshInfo(boolean printMeshInfo) { this.printMeshInfo = printMeshInfo; } @Override public List find(float[] start, float[] end, float[] extents) { List pathRet = new ArrayList<>(); //获取开始点,附近的点 FindNearestPolyResult startResult = query.findNearestPoly(start, extents, filter); //获取结束点,附近的点 FindNearestPolyResult endResult = query.findNearestPoly(end, extents, filter); //寻找开始点和结束点附近的多边形 if (startResult.getNearestRef() == 0 || endResult.getNearestRef() == 0) { LOGGER.info("start or end point not found poly"); return Collections.emptyList(); } //获取路径ids FindPathResult path = query.findPath(startResult.getNearestRef(), endResult.getNearestRef(), startResult.getNearestPos(), endResult.getNearestPos(), filter); if (path.getStatus() != Status.SUCCSESS) { LOGGER.info("nav path status is {}", path.getStatus()); return Collections.emptyList(); } //路径平滑 List straightPath = query.findStraightPath(startResult.getNearestPos(), endResult.getNearestPos(), path.getRefs(), Integer.MAX_VALUE, 0); for (StraightPathItem straightPathItem : straightPath) { float[] p = new float[3]; System.arraycopy(straightPathItem.getPos(), 0, p, 0, straightPathItem.getPos().length); pathRet.add(p); } return pathRet; } @Override public List find(Vector3f start, Vector3f end, Vector3f extents) { List paths = find(v3fToFArr(start), v3fToFArr(end), v3fToFArr(extents)); return paths.stream().map(p -> new Vector3f(p[0], p[1], p[2])).collect(Collectors.toList()); } @Override public List find(float[] start, float[] end) { return find(start, end, extents); } @Override public List find(Vector3f start, Vector3f end) { return find(start, end, FArrTov3f(extents)); } @Override public float[] raycast(float[] start, float[] end, float[] extents) { FindNearestPolyResult startResult = query.findNearestPoly(start, extents, filter); if (startResult.getNearestRef() == 0) { throw new IllegalArgumentException("not find nearestPoly"); } float[] hitPoint = null; RaycastHit hitReasult = query.raycast(startResult.getNearestRef(), startResult.getNearestPos(), end, filter, 0, 0); if (hitReasult.t > 1) { return end; } else { hitPoint = new float[3]; hitPoint[0] = start[0] + (end[0] - start[0]) * hitReasult.t; hitPoint[1] = start[1] + (end[1] - start[1]) * hitReasult.t; hitPoint[2] = start[2] + (end[2] - start[2]) * hitReasult.t; } return hitPoint; } @Override public Vector3f raycast(Vector3f start, Vector3f end, Vector3f extents) { float[] point = raycast(v3fToFArr(start), v3fToFArr(end), v3fToFArr(extents)); return FArrTov3f(point); } @Override public float[] raycast(float[] start, float[] end) { return raycast(start, end, new float[]{0.f, 2.f, 0.f}); } @Override public Vector3f raycast(Vector3f start, Vector3f end) { return raycast(start, end, FArrTov3f(extents)); } @Override public float[] findNearest(float[] point) { FindNearestPolyResult result = query.findNearestPoly(point, extents, filter); return result.getNearestPos(); } @Override public Vector3f findNearest(Vector3f point) { float[] p = findNearest(v3fToFArr(point)); return FArrTov3f(p); } @Override public float[] findNearest(float[] point, float[] extents) { FindNearestPolyResult result = query.findNearestPoly(point, extents, filter); return result.getNearestPos(); } @Override public Vector3f findNearest(Vector3f point, Vector3f extents) { float[] p = findNearest(v3fToFArr(point), v3fToFArr(extents)); return FArrTov3f(p); } private static float[] v3fToFArr(Vector3f vector3f) { float[] arr = new float[3]; arr[0] = vector3f.getX(); arr[1] = vector3f.getY(); arr[2] = vector3f.getZ(); return arr; } private static Vector3f FArrTov3f(float[] point) { return new Vector3f(point[0], point[1], point[2]); } } ================================================ FILE: gamioo-navigation/src/main/java/io/gamioo/nav/INav.java ================================================ package io.gamioo.nav; import io.gamioo.common.vector.Vector3f; import java.util.List; /** * */ public interface INav { /** * 寻找路径 * * @param start 开始坐标点 * @param end 结束坐标点 * @return 路经典集合 */ List find(float[] start, float[] end); List find(Vector3f start, Vector3f end); /** * 光线照射发,寻找可以支线通过的hit点,如果可通过则返回hit * * @param start 开始点 * @param end 目标点 * @return 光照可以达到的点 */ float[] raycast(float[] start, float[] end); Vector3f raycast(Vector3f start, Vector3f end); /** * 获取指定点附近可行走的点 * * @param point 当前验证点 * @return 可以行走的点(如验证点不能行走 , 则返回可以行走的点) */ float[] findNearest(float[] point); Vector3f findNearest(Vector3f point); default float[] vector2Point(Vector3f vector3f) { float[] arr = new float[3]; arr[0] = vector3f.getX(); arr[1] = vector3f.getY(); arr[2] = vector3f.getZ(); return arr; } default Vector3f point2Vector(float[] point) { return new Vector3f(point[0], point[1], point[2]); } } ================================================ FILE: gamioo-navigation/src/main/java/io/gamioo/nav/Main.java ================================================ package io.gamioo.nav; import java.io.IOException; public class Main { public static void main(String[] args) throws IOException { float[] extents = {1.f, 1.f, 1.f}; String navFilePath = "solo_navmesh.bin"; //初始化寻路对象 Easy3dNav easyNav = new Easy3dNav(); //默认为true,可以忽略 easyNav.setUseU3dData(false); //默认为false,查看需要设置为true easyNav.setPrintMeshInfo(false); easyNav.setExtents(extents); // File file = FileUtils.getFile(navFilePath); // assert file != null; easyNav.init(navFilePath); } } ================================================ FILE: gamioo-navigation/src/main/java/io/gamioo/nav/NavEngine.java ================================================ package io.gamioo.nav; import io.gamioo.common.util.FileUtils; import io.gamioo.common.util.NativeUtils; import io.gamioo.common.vector.Vector3f; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; /** * 3D NavMesh 寻路导航 * * @author Allen Jiang */ public class NavEngine implements INav { private static final Logger logger = LogManager.getLogger(NavEngine.class); private int id; private float[] extents = {1.f, 1.f, 1.f}; static { try { NativeUtils.loadLibrary("recast"); } catch (Throwable e) { logger.error(e.getMessage(), e); System.exit(1); } } public void init(int id, String filePath) throws IOException { try { byte[] content = FileUtils.getByteArrayFromFile(filePath); this.id = this.load(id, content, content.length); } catch (Exception e) { logger.error(e.getMessage(), e); } if (this.id < 0) { throw new IOException("load map failed,mapId" + id); } } /** * 加载地图 * * @param navmeshId 寻路数据地图ID * @param content 地图文件的路径例 * @param length 数据长度 * @return navmeshId, 为0或负数表示加载失败,为正数表示加载成功,后续寻路时传入此id为参数 */ public native int load(int navmeshId, byte[] content, int length); /** * 寻路 * * @param navmeshId 寻路数据地图ID * @param startX 起始点X * @param startY 起始点Y * @param startZ 起始点Z * @param endX 结束点X * @param endY 结束点Y * @param endZ 结束点Z * @return 返回路径点列表,注意,查找不到时,会返回空 */ public native float[] find(int navmeshId, float startX, float startY, float startZ, float endX, float endY, float endZ); /** * 找到目标点最近的静态可达点 * * @param navmeshId 寻路数据地图ID * @param pointX 参考点X * @param pointY 参考点Y * @param pointZ 参考点Z * @return 如果目标点可达,返回目标点即可, 如果搜索范围内没有,返回空 */ public native float[] findNearest(int navmeshId, float pointX, float pointY, float pointZ); /** * 光线照射发,寻找可以支线通过的hit点,如果可通过则返回hit * * @param navmeshId 寻路数据地图ID * @param startX 起始点X * @param startY 起始点Y * @param startZ 起始点Z * @param endX 结束点X * @param endY 结束点Y * @param endZ 结束点Z * @return 返回射线射过去遇到的第一个阻挡点,如果到终点都没有阻挡,返回终点 */ public native float[] raycast(int navmeshId, float startX, float startY, float startZ, float endX, float endY, float endZ); /** * 释放加载的地图数据 * * @param navmeshId 寻路数据地图ID */ public native void release(int navmeshId); /** * 释放加载的所有地图数据 */ public native void releaseAll(); @Override public List find(float[] start, float[] end) { List ret = new ArrayList<>(); float[] array = this.find(this.id, start[0], start[1], start[2], end[0], end[1], end[2]); if (array == null) { return ret; } int length = array.length / 3; for (int i = 0; i < length; i++) { float[] point = new float[3]; int index = i * 3; point[0] = array[index]; point[1] = array[index + 1]; point[2] = array[index + 2]; ret.add(point); } return ret; } @Override public List find(Vector3f start, Vector3f end) { List paths = find(vector2Point(start), vector2Point(end)); return paths.stream().map(p -> new Vector3f(p[0], p[1], p[2])).collect(Collectors.toList()); } @Override public float[] raycast(float[] start, float[] end) { return this.raycast(id, start[0], start[1], start[2], end[0], end[1], end[2]); } @Override public Vector3f raycast(Vector3f start, Vector3f end) { Vector3f ret = null; float[] point = this.raycast(vector2Point(start), vector2Point(end)); if (point != null) { ret = point2Vector(point); } return ret; } @Override public float[] findNearest(float[] point) { return this.findNearest(id, point[0], point[1], point[2]); } @Override public Vector3f findNearest(Vector3f point) { Vector3f ret = null; float[] p = this.findNearest(vector2Point(point)); if (p != null) { ret = point2Vector(p); } return ret; } } ================================================ FILE: gamioo-navigation/src/test/java/io/gamioo/nav/NavEngineTest.java ================================================ package io.gamioo.nav; import com.github.silencesu.Easy3dNav.Easy3dNav; import io.gamioo.common.util.FileUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.junit.jupiter.api.*; import java.io.File; import java.io.IOException; import java.util.List; @DisplayName("Recast native test") @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class NavEngineTest { private static final Logger logger = LogManager.getLogger(NavEngineTest.class); private static NavEngine nav; private static Easy3dNav easyNav = new Easy3dNav(); private static String navFilePath = "solo_navmesh.bin"; private static float[] extents = {1.f, 1.f, 1.f}; // private static float[] recastExtends = {0.0f, 1.0f, 0.0f}; public static int id; float[] src = new float[]{-112.082f, 0.359222f, 55.5665f}; float[] end = new float[]{-49.0154f, 0.0922241f, 104.259f}; float[] target = new float[]{-86.6f, 1.8f, 53.6f}; @BeforeAll public static void beforeAll() throws IOException { nav = new NavEngine(); id = 1; nav.init(id, navFilePath); //初始化寻路对象 easyNav = new Easy3dNav(); //默认为true,可以忽略 easyNav.setUseU3dData(false); //默认为false,查看需要设置为true easyNav.setPrintMeshInfo(false); easyNav.setExtents(extents); File file = FileUtils.getFile(navFilePath); assert file != null; easyNav.init(file.getAbsolutePath()); } @AfterAll public static void afterAll() { nav.release(id); nav.releaseAll(); } @DisplayName("C++寻路") @Test @Order(2) public void nativeFind() throws Exception { List array = nav.find(src, end); logger.debug("c++ find point {} array={} ", array.size(), array); } @DisplayName("Java寻路") @Test @Order(3) public void javaFind() throws Exception { List array = easyNav.find(src, end); logger.debug("java find point {} array={} ", array.size(), array); } @DisplayName("C++射线") @Test @Order(4) public void nativeRaycast() { float[] point = nav.raycast(src, end); logger.debug("c++ raycast point={}", point); } @DisplayName("java射线") @Test @Order(5) public void javaRaycast() { float[] point = easyNav.raycast(src, end); logger.debug("java raycast point={}", point); } @DisplayName("C++寻找最近的可通点") @Test @Order(6) public void nativeFindNearest() { float[] point = nav.findNearest(target); logger.debug("c++ findNearest point={}", point); } @DisplayName("java寻找最近的可通点") @Test @Order(7) public void javaFindNearest() { float[] point = easyNav.findNearest(target); logger.debug("java findNearest point={}", point); } } ================================================ FILE: gamioo-network/.settings/.gitignore ================================================ /org.eclipse.buildship.core.prefs ================================================ FILE: gamioo-network/build.gradle ================================================ dependencies { implementation project(':gamioo-common'); implementation project(':gamioo-protocol'); api group: 'io.netty', name: 'netty-all', version: '4.1.89.Final'; } ================================================ FILE: gamioo-network/src/main/java/io/gamioo/network/package-info.java ================================================ package io.gamioo.network; ================================================ FILE: gamioo-network/src/main/java/io/gamioo/network/util/IPUtil.java ================================================ package io.gamioo.network.util; import io.gamioo.common.exception.ServiceException; import io.netty.channel.Channel; import io.netty.handler.codec.http.FullHttpRequest; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.SystemUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import javax.servlet.http.HttpServletRequest; import java.net.Inet4Address; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.NetworkInterface; import java.util.Enumeration; import java.util.regex.Matcher; import java.util.regex.Pattern; public class IPUtil { private static final Logger logger = LogManager.getLogger(IPUtil.class); // SiteLocalAddress=false,LoopbackAddress=false,address.getHostAddress()=115.29.176.102 // 广域网IP // SiteLocalAddress=true,LoopbackAddress=false,address.getHostAddress()=10.161.175.91 // 局域网IP // SiteLocalAddress=false,LoopbackAddress=true,address.getHostAddress()=127.0.0.1 // 回环IP // 获取局域网IP public static String getIP() throws ServiceException { String ret = ""; try { if (SystemUtils.IS_OS_LINUX) { Enumeration en = NetworkInterface.getNetworkInterfaces(); while (en.hasMoreElements()) { NetworkInterface ni = en.nextElement(); Enumeration enIp = ni.getInetAddresses(); while (enIp.hasMoreElements()) { InetAddress inet = enIp.nextElement(); if (inet.isSiteLocalAddress() && !inet.isLoopbackAddress() && (inet instanceof Inet4Address)) { ret = inet.getHostAddress().toString(); // logger.info("SiteLocalAddress={},host={}",inet.isSiteLocalAddress(), // inet.getHostAddress().toString()); } } } } else { ret = InetAddress.getLocalHost().getHostAddress(); } } catch (Exception e) { logger.error(e.getMessage(), e); } if (StringUtils.isEmpty(ret)) { throw new ServiceException("获取本地IP失败"); } return ret; } // 将127.0.0.1形式的IP地址转换成十进制整数,这里没有进行任何错误处理 // 将IP地址转换为主机字节序 public static long ipToLong(String strIp) { long[] ip = new long[4]; // 先找到IP地址字符串中.的位置 int position1 = strIp.indexOf("."); int position2 = strIp.indexOf(".", position1 + 1); int position3 = strIp.indexOf(".", position2 + 1); // 将每个.之间的字符串转换成整型 ip[0] = Long.parseLong(strIp.substring(0, position1)); ip[1] = Long.parseLong(strIp.substring(position1 + 1, position2)); ip[2] = Long.parseLong(strIp.substring(position2 + 1, position3)); ip[3] = Long.parseLong(strIp.substring(position3 + 1)); return (ip[0] << 24) + (ip[1] << 16) + (ip[2] << 8) + ip[3]; } // 将十进制整数形式转换成127.0.0.1形式的ip地址 // 主机字节序转换为将IP地址 public static String longToIP(long longIp) { StringBuffer sb = new StringBuffer(""); // 直接右移24位 sb.append(String.valueOf((longIp >>> 24))); sb.append("."); // 将高8位置0,然后右移16位 sb.append(String.valueOf((longIp & 0x00FFFFFF) >>> 16)); sb.append("."); // 将高16位置0,然后右移8位 sb.append(String.valueOf((longIp & 0x0000FFFF) >>> 8)); sb.append("."); // 将高24位置0 sb.append(String.valueOf((longIp & 0x000000FF))); return sb.toString(); } /** * @param ip IP * @return 返回是否是内网 * 判断是否为内网IP */ public static boolean isInner(String ip) { String reg = "(10|172|192)\\.([0-1][0-9]{0,2}|[2][0-5]{0,2}|[3-9][0-9]{0,1})\\.([0-1][0-9]{0,2}|[2][0-5]{0,2}|[3-9][0-9]{0,1})\\.([0-1][0-9]{0,2}|[2][0-5]{0,2}|[3-9][0-9]{0,1})";// 正则表达式=。 // =、懒得做文字处理了、 Pattern p = Pattern.compile(reg); Matcher matcher = p.matcher(ip); return matcher.find(); } public static String getRemortIP(Channel channel) { String ip = null; InetSocketAddress remoteAddr = (InetSocketAddress) channel.remoteAddress(); if (remoteAddr != null) { ip = remoteAddr.getAddress().getHostAddress(); } return ip; } /** * 兼容反向代理后的客户端真实地址 * * @param request HTTP请求 * @return 返回IP */ public static String getRemoteIP(HttpServletRequest request) { String ret = request.getHeader("x-forwarded-for"); if (ret == null) { return request.getRemoteAddr(); } // 处理多IP绑定的问题 "219.75.62.162, 10.168.82.130, 220.255.1.138" // 10.125.117.199, 119.233.254.49 if (ret.indexOf(",") >= 0) { ret = StringUtils.trim(StringUtils.substringAfterLast(ret, ",")); } return ret; } public static String getRemoteIP(FullHttpRequest httpRequest) { String ip = null; try { ip = httpRequest.headers().get("X-Real_IP"); if (StringUtils.isEmpty(ip)) { ip = httpRequest.headers().get("x-forwarded-for"); // 处理多IP绑定的问题 "219.75.62.162, 10.168.82.130, 220.255.1.138" // 10.125.117.199, 119.233.254.49 if (StringUtils.indexOf(ip, ",") >= 0) { ip = StringUtils.trim(StringUtils.substringAfterLast(ip, ",")); } } } catch (Exception e) { logger.error(e.getMessage(), e); } return ip; } public static void main(String[] args) { String ipStr = "127.0.0.1"; long longIp = IPUtil.ipToLong(ipStr); System.out.println("整数形式为:" + longIp); System.out.println("整数" + longIp + "转化成字符串IP地址:" + IPUtil.longToIP(longIp)); // ip地址转化成二进制形式输出 System.out.println("二进制形式为:" + Long.toBinaryString(longIp)); } } ================================================ FILE: gamioo-network/src/test/java/io/gamioo/network/MainT.java ================================================ package io.gamioo.network; public class MainT { public static void main(String[] args) { System.out.println("hello ketty11"); } } ================================================ FILE: gamioo-orm/.settings/.gitignore ================================================ /org.eclipse.buildship.core.prefs ================================================ FILE: gamioo-orm/build.gradle ================================================ dependencies { implementation project(':gamioo-common'); } ================================================ FILE: gamioo-orm/src/main/java/io/gamioo/orm/OrmService.java ================================================ package io.gamioo.orm; /** * @author Allen Jiang */ public class OrmService { } ================================================ FILE: gamioo-orm/src/main/java/io/gamioo/orm/package-info.java ================================================ package io.gamioo.orm; ================================================ FILE: gamioo-orm/src/test/java/io/gamioo/orm/MainT.java ================================================ package io.gamioo.orm; public class MainT { public static void main(String[] args) { System.out.println("hello ketty11"); } } ================================================ FILE: gamioo-protocol/.settings/.gitignore ================================================ /org.eclipse.buildship.core.prefs ================================================ FILE: gamioo-protocol/build.gradle ================================================ dependencies { implementation project(':gamioo-common'); implementation group: 'com.google.protobuf', name: 'protobuf-java', version: '3.16.3'; } ================================================ FILE: gamioo-protocol/src/main/java/io/gamioo/protocol/ProtocolService.java ================================================ package io.gamioo.protocol; /** * @author Allen Jiang */ public class ProtocolService { } ================================================ FILE: gamioo-protocol/src/main/java/io/gamioo/protocol/package-info.java ================================================ package io.gamioo.protocol; ================================================ FILE: gamioo-protocol/src/test/java/io/gamioo/protocol/MainT.java ================================================ package io.gamioo.protocol; public class MainT { public static void main(String[] args) { System.out.println("hello ketty11"); } } ================================================ FILE: gamioo-redis/.settings/.gitignore ================================================ /org.eclipse.buildship.core.prefs ================================================ FILE: gamioo-redis/build.gradle ================================================ dependencies { implementation project(':gamioo-common'); implementation 'it.unimi.dsi:fastutil:8.3.0' implementation 'redis.clients:jedis:3.3.0'; implementation group: 'io.lettuce', name: 'lettuce-core', version: '6.2.3.RELEASE'; } ================================================ FILE: gamioo-redis/src/main/java/io/gamioo/redis/Redis.java ================================================ package io.gamioo.redis; import io.gamioo.common.exception.ServiceException; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import redis.clients.jedis.*; import java.util.*; /** * Redis操作类. * * @since 1.0 */ public class Redis { private static final Logger logger = LogManager.getLogger(Redis.class); /** * 默认超时(毫秒) */ public static final int DEFAULT_TIMEOUT = 5000; /** * 默认database */ public static final int DEFAULT_DATABASE = 0; /** * jedis client 池 */ private JedisPool pool; public Redis(String host, int port) { this(host, port, DEFAULT_DATABASE); } public Redis(String host, int port, int database) { this(host, port, DEFAULT_TIMEOUT, null, database); } public Redis(String host, int port, String password) { this(host, port, DEFAULT_TIMEOUT, password, DEFAULT_DATABASE); } /** * 初始化Redis辅助类. * * @param host IP * @param port 端口 * @param index 库索引 */ public Redis(String host, int port, String password, int index) { // 通过config配置连接池参数 this(host, port, DEFAULT_TIMEOUT, password, index); } /** * 初始化Redis辅助类. * * @param host IP * @param port 端口 * @param index 库Index */ public Redis(String host, int port, int timeout, String password, int index) { // 通过config配置连接池参数 this(GenericObjectPoolConfig.DEFAULT_MAX_TOTAL, host, port, timeout, password, index); } /** * @param connection 连接数 */ public Redis(int connection, String host, int port, int timeout, String password, int database) { GenericObjectPoolConfig config = new GenericObjectPoolConfig(); config.setMaxTotal(connection); config.setMaxIdle(connection); pool = new JedisPool(config, host, port, timeout, password, database); logger.info("Redis info:host={},port={},database={},timeout={}ms,password={}", host, port, database, timeout, password); ping(); } public void ping() { try (Jedis j = pool.getResource()) { String ret = j.ping(); if ("PONG".equals(ret)) {//yes logger.info("Redis server connected ok"); } else { logger.info("Redis server connected failed"); } } catch (Exception e) { throw new ServiceException(e); } } // // ------------------------------Key相关命令------------------------------ // public String type(String key) { try (Jedis j = pool.getResource()) { String ret = j.type(key); return ret; } catch (Exception e) { throw new ServiceException(e); } } public Long del(String key) { try (Jedis j = pool.getResource()) { Long result = j.del(key); return result; } catch (Exception e) { throw new ServiceException(e); } } public String debug(String key) { try (Jedis j = pool.getResource()) { String result = j.debug(DebugParams.OBJECT(key)); return result; } catch (Exception e) { throw new ServiceException(e); } } public void del(byte[] key) { try (Jedis j = pool.getResource()) { j.del(key); } catch (Exception e) { throw new ServiceException(e); } } /** * 删除指定的Key. *

* 时间复杂度 o(N),N为要移除Key的数量.
* 如果Key不存在,则直接忽略. * * @param keys Key列表 * @return 被删除Keys的数量 */ public long del(String... keys) { if (keys == null || keys.length == 0) { return 0L; } long ret = 0L; try (Jedis j = pool.getResource()) { ret = j.del(keys); } catch (Exception e) { throw new ServiceException(e); } return ret; } /** * 批量删除 由 (key + assistKey) 的组合 key * * @param key * @param datas */ // public void batchDel(String key,String... assistKeys){ // try (Jedis j = pool.getResource()){ // Pipeline pipeline = j.pipelined(); // for(String assistKey : assistKeys){ // String delKey = key + assistKey; // pipeline.del(delKey); // } // pipeline.exec(); // // } catch (Exception e) { // // throw new ServiceException(e); // } // } /** * 查找所有匹配给定的模式的Key. * * @param pattern 模式 * @return Key集合 * warning KEYS 命令被用于处理一个大的数据库时,它们可能会阻塞服务器达数秒之久。 */ public Set keys(String pattern) { try (Jedis j = pool.getResource()) { Set ret = j.keys(pattern); return ret; } catch (Exception e) { throw new ServiceException(e); } } /** * 查找所有匹配给定的模式的Key. * * @param pattern 模式 * @return Key集合 */ public Set keys(byte[] pattern) { try (Jedis j = pool.getResource()) { Set ret = j.keys(pattern); return ret; } catch (Exception e) { throw new ServiceException(e); } } /** * 设计Key的过期时间。 *

* 如果Key已过期,将会被自动删除,设置了过期时间的Key被称之为volatile(不稳定) KEY.
* * @param key KEY * @param seconds 过期时间(秒) * @return 如果设置了过期时间返回1,没有设置或不能设置过期时间返回0 */ public long expire(String key, int seconds) { long ret = 0L; try (Jedis j = pool.getResource()) { ret = j.expire(key, seconds); } catch (Exception e) { throw new ServiceException(e); } return ret; } /** * 移除生存时间 */ public long persist(String key) { try (Jedis j = pool.getResource()) { long ret = j.persist(key); return ret; } catch (Exception e) { throw new ServiceException(e); } } /** * 获取生存时间 */ public long ttl(String key) { try (Jedis j = pool.getResource()) { long ret = j.ttl(key); return ret; } catch (Exception e) { throw new ServiceException(e); } } // // ------------------------------String相关命令------------------------------ // /** * 设置key所对应的value值. *

* 时间复杂度为o(1)
* 如果Key已存在了,它会被覆盖,而不管它是什么类型。
* 这里干掉了返回值,因为SET不会失败,总返回OK * * @param key KEY值 * @param value KEY对应的Value */ public void set(String key, String value) { try (Jedis j = pool.getResource()) { j.set(key, value); } catch (Exception e) { throw new ServiceException(e); } } /** * 将key重命名为newKey.如果key与newkey相同,将返回错误,如果newkey已经存在,则值将被覆盖 */ public void rename(String key, String newKey) { try (Jedis j = pool.getResource()) { j.rename(key, newKey); } catch (Exception e) { throw new ServiceException(e); } } /** * 获取key所对应的value值. *

* 时间复杂度为o(1)
* 如果key不存在,返回null.
* 如果key的value值不是String类型,就返回错误,因为Get只处理String类型的Values. * * @param key Key值 * @return 如果Key存在,则返回对应的Value值. */ public String get(String key) { try (Jedis j = pool.getResource()) { String ret = j.get(key); return ret; } catch (Exception e) { throw new ServiceException(e); } } /** * 判断指定Key是否存在. *

* 时间复杂度o(1)
* * @param key Key值 * @return 如果存在,则返回true. */ public boolean exists(String key) { boolean ret = false; try (Jedis j = pool.getResource()) { ret = j.exists(key); } catch (Exception e) { throw new ServiceException(e); } return ret; } /** * @param value the decrement value * @return the value of key after the decrement */ public long decrBy(String key, long value) { Long ret = null; try (Jedis j = pool.getResource()) { ret = j.decrBy(key, value); return ret; } catch (Exception e) { throw new ServiceException(e); } } /** * 将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位) */ public String setex(String key, int seconds, String value) { try (Jedis j = pool.getResource()) { String ret = j.setex(key, seconds, value); return ret; } catch (Exception e) { throw new ServiceException(e); } } public String set(byte[] key, byte[] value) { try (Jedis j = pool.getResource()) { String ret = j.set(key, value); return ret; } catch (Exception e) { throw new ServiceException(e); } } public String setex(byte[] key, int seconds, byte[] value) { try (Jedis j = pool.getResource()) { String ret = j.setex(key, seconds, value); return ret; } catch (Exception e) { throw new ServiceException(e); } } public byte[] get(byte[] key) { try (Jedis j = pool.getResource()) { byte[] ret = j.get(key); return ret; } catch (Exception e) { throw new ServiceException(e); } } public List mget(String... keys) { try (Jedis j = pool.getResource()) { List ret = j.mget(keys); return ret; } catch (Exception e) { throw new ServiceException(e); } } public List mget(byte[]... keys) { try (Jedis j = pool.getResource()) { List ret = j.mget(keys); return ret; } catch (Exception e) { throw new ServiceException(e); } } public String mset(String... kvs) { try (Jedis j = pool.getResource()) { String ret = j.mset(kvs); return ret; } catch (Exception e) { throw new ServiceException(e); } } /** * 时间复杂度: O(1) 返回值: 执行 INCR 命令之后 key 的值。 */ public long incr(String key) { try (Jedis j = pool.getResource()) { long ret = j.incr(key); return ret; } catch (Exception e) { throw new ServiceException(e); } } // // ------------------------------Hash相关命令------------------------------ // /** * 时间复杂度: O(1)。 返回 key 指定的哈希集包含的字段的数量。 返回值 整数:哈希集中字段的数量,当 key 指定的哈希集不存在时返回 0 */ public long hlen(String key) { long ret = 0; try (Jedis j = pool.getResource()) { ret = j.hlen(key); } catch (Exception e) { throw new ServiceException(e); } return ret; } /** * 时间复杂度: O(1)。 返回字段是否是 key 指定的哈希集中存在的字段。 */ public boolean hexists(String key, String field) { boolean ret = false; try (Jedis j = pool.getResource()) { ret = j.hexists(key, field); } catch (Exception e) { throw new ServiceException(e); } return ret; } /** * Hash操作,设置Key指定的Hash集中指定字段的值. *

* 时间复杂度为o(N),其中N是被设置的字段数量.
* 该命令将重写所有在Hash集中存在字段,如果key指定的Hash集不存在,会创建一个新的Hash集并与key关联 * * @param key Key键 * @param value Hash集 * @return 状态码 */ public String hmset(String key, Map value) { String ret = null; try (Jedis j = pool.getResource()) { ret = j.hmset(key, value); } catch (Exception e) { throw new ServiceException(e); } return ret; } /** * Hash操作,设置Key指定的Hash集中指定字段的值. * * @param datas * @param key * @param field * @param value */ public void hmset(Collection datas, String key, String field, String value) { try (Jedis j = pool.getResource()) { Pipeline pipeline = j.pipelined(); for (Object element : datas) { String keyStr = key + element.toString(); pipeline.hset(keyStr, field, value); } pipeline.exec(); } catch (Exception e) { throw new ServiceException(e); } } /** * Hash操作,设置Key指定的Hash集中指定字段的值. *

* 时间复杂度为o(N),其中N是被设置的字段数量.
* 该命令将重写所有在Hash集中存在字段,如果key指定的Hash集不存在,会创建一个新的Hash集并与key关联 * * @param key Key键 * @param value Hash集 * @return 状态码 */ public String hmset(byte[] key, Map value) { String ret = null; try (Jedis j = pool.getResource()) { ret = j.hmset(key, value); } catch (Exception e) { throw new ServiceException(e); } return ret; } /** * 获取Key对应的Hash集中该字段所关联值的列表. *

* 时间复杂度o(N),其中N是被请求字段的数量
* * @param key Key值 * @param fields 指定字段 * @return 如果存在此字段,则返回所关联的值列表,否则返回空列表. */ public List hmget(String key, String... fields) { List ret = null; try (Jedis j = pool.getResource()) { ret = j.hmget(key, fields); } catch (Exception e) { ret = Collections.emptyList(); throw new ServiceException(e); } return ret; } /** * 返回哈希表 key 中的所有域(所有的field集合). *

* 时间复杂度:
* O(N), N 为哈希表的大小 * * @param key * @return 一个包含哈希表中所有域的表.
* 当 key 不存在时,返回一个空表 */ public Set hkeys(String key) { Set ret = null; try (Jedis j = pool.getResource()) { ret = j.hkeys(key); } catch (Exception e) { ret = Collections.emptySet(); throw new ServiceException(e); } return ret; } /** * 返回哈希表 key 中所有域的值. *

* 时间复杂度o(N), N 为哈希表的大小
* * @param key Key值 * @return 一个包含哈希表中所有值的表;当 key 不存在时,返回一个空表. */ public List hvals(String key) { List ret = null; try (Jedis j = pool.getResource()) { ret = j.hvals(key); } catch (Exception e) { ret = Collections.emptyList(); throw new ServiceException(e); } return ret; } /** * 获取Key对应的Hash集中该字段所关联的值. *

* 时间复杂度o(1)
* * @param key Key值 * @param field 指定字段 * @return 如果存在此字段,则返回所关联的值,否则返回null */ public String hget(String key, String field) { try (Jedis j = pool.getResource()) { String ret = j.hget(key, field); return ret; } catch (Exception e) { throw new ServiceException(e); } } /** * 获取Key对应的Hash集中该字段所关联的值. *

* 时间复杂度o(1)
* * @param key Key值 * @param field 指定字段 * @return 如果存在此字段,则返回所关联的值,否则返回null */ public byte[] hget(byte[] key, byte[] field) { try (Jedis j = pool.getResource()) { byte[] ret = j.hget(key, field); return ret; } catch (Exception e) { throw new ServiceException(e); } } /** * Hash操作,设置Key指定的Hash集中指定字段的值. */ public Long hset(String key, String field, String value) { Long ret = null; try (Jedis j = pool.getResource()) { ret = j.hset(key, field, value); } catch (Exception e) { throw new ServiceException(e); } return ret; } /** * Hash操作,设置Key指定的Hash集中指定字段的值(如果key不存在则设置成功). */ public Long hsetnx(String key, String field, String value) { Long ret = null; try (Jedis j = pool.getResource()) { ret = j.hsetnx(key, field, value); } catch (Exception e) { throw new ServiceException(e); } return ret; } /** * Hash操作,设置Key指定的Hash集中指定字段的值. */ public Long hset(byte[] key, byte[] field, byte[] value) { Long ret = null; try (Jedis j = pool.getResource()) { ret = j.hset(key, field, value); } catch (Exception e) { throw new ServiceException(e); } return ret; } /** * 获取key指定的Hash集中所有的字段和值。 *

* 时间复杂度o(N),其中N是Hash集的大小。
* 使用命令行时请注意:
* 返回值中,每个字段名的下一个是它的值,所以返回值的长度是Hash集大小的两倍. * * @param key Key值 * @return 如果key所对应的Hash集存在,则返回此集合,否则返回空列表. */ public Map hgetAll(String key) { Map ret = null; try (Jedis j = pool.getResource()) { ret = j.hgetAll(key); } catch (Exception e) { ret = Collections.emptyMap(); throw new ServiceException(e); } return ret; } /** * 获取key指定的Hash集中所有的字段和值。 *

* 时间复杂度o(N),其中N是Hash集的大小。
* 使用命令行时请注意:
* 返回值中,每个字段名的下一个是它的值,所以返回值的长度是Hash集大小的两倍. * * @param key Key值 * @return 如果key所对应的Hash集存在,则返回此集合,否则返回空列表. */ public Map hgetAll(byte[] key) { Map ret = null; try (Jedis j = pool.getResource()) { ret = j.hgetAll(key); } catch (Exception e) { ret = Collections.emptyMap(); throw new ServiceException(e); } return ret; } /** * 删除Key所对应的Hash集中指定field字段. *

* 时间复杂度 o(N),其中N为要移除字段的数量.
* 如果指定字段不存在,则忽略处理,如果指定的Hash集不存在,应该指令返回0.
* 此指令可以返回被成功删除字段的数量,但目前没有需求,就不返回了. * * @param key Key值 * @param field 指定要删除的字段集合 * @return If the field was present in the hash it is deleted and 1 is * returned, otherwise 0 is returned and no operation is performed. */ public long hdel(String key, String... field) { try (Jedis j = pool.getResource()) { long ret = j.hdel(key, field); return ret; } catch (Exception e) { throw new ServiceException(e); } } /** * 增加Key所对应的Hash集中指定字段的数值. *

* 时间复杂度为o(1)
* 如果Key不存在,会创建一个新的Hash集并与之关联。
* 如果指定字段不存在,则字段的值在该操作执行前被设计为0
* 注意:此命令支持的值范围限定在64位 有符号整数 * * @param key Key值 * @param field 指定字段 * @param value 要加的值 * @return 增值操作执行后该字段的值 */ public long hincrBy(String key, String field, long value) { long ret = 0L; try (Jedis j = pool.getResource()) { ret = j.hincrBy(key, field, value); } catch (Exception e) { throw new ServiceException(e); } return ret; } // // ------------------------------List 有序的相关命令------------------------------ // /** * 删除列表里最右边的元素。 *

* * @param key KEY值 * @return 最右边的元素 */ public String rpop(String key) { try (Jedis j = pool.getResource()) { String ret = j.rpop(key); return ret; } catch (Exception e) { throw new ServiceException(e); } } /** * 获得列表的长度。 *

* * @param key KEY值 * @return 成员数量 */ public long llen(String key) { try (Jedis j = pool.getResource()) { long ret = j.llen(key); return ret; } catch (Exception e) { throw new ServiceException(e); } } /** * 获得列表所有元素。 *

* * @param key KEY值 * @param start 列表起始位置 * @param end 列表结束位置 * @return 列表所有元素 */ public List lrange(String key, long start, long end) { List ret = null; try (Jedis j = pool.getResource()) { ret = j.lrange(key, start, end); } catch (Exception e) { ret = Collections.emptyList(); throw new ServiceException(e); } return ret; } public List lrange(byte[] key, int start, int end) { try (Jedis j = pool.getResource()) { List ret = j.lrange(key, start, end); return ret; } catch (Exception e) { throw new ServiceException(e); } } /** * 返回列表key中,小标为index的元素 *

* * @param key KEY值 * @param index VALUE值 * @return 成员数量 */ public String lindex(String key, long index) { try (Jedis j = pool.getResource()) { String ret = j.lindex(key, index); return ret; } catch (Exception e) { throw new ServiceException(e); } } // /** // * 返回列表长度 // *

// * // * @param key // * KEY值 // * @param value // * VALUE值 // * @param pivot // * 位于这个值之前或者之后 // * @return 成员数量 // */ // public long linsert(String key, BinaryClient.LIST_POSITION where, String pivot, String value) { // try (Jedis j = pool.getResource()) { // long ret = j.linsert(key, where, pivot, value); // return ret; // } catch (Exception e) { // throw new ServiceException(e); // } // } /** * 返回被移除的数量 *

* * @param key KEY值 * @param value VALUE值 * @return 被移除的数量 */ public long lrem(String key, String value) { try (Jedis j = pool.getResource()) { long ret = j.lrem(key, 1, value); return ret; } catch (Exception e) { throw new ServiceException(e); } } /** * 从列表左边添加一个元素。 *

* * @param key KEY值 * @param value VALUE值 * @return 成员数量 */ public long lpush(String key, String... value) { try (Jedis j = pool.getResource()) { long ret = j.lpush(key, value); return ret; } catch (Exception e) { throw new ServiceException(e); } } public long lpush(byte[] key, byte[] value) { try (Jedis j = pool.getResource()) { long ret = j.lpush(key, value); return ret; } catch (Exception e) { throw new ServiceException(e); } } public long rpush(String key, String... value) { try (Jedis j = pool.getResource()) { long ret = j.rpush(key, value); return ret; } catch (Exception e) { throw new ServiceException(e); } } public List blpop(int timeout, String... key) { try (Jedis j = pool.getResource()) { List ret = j.blpop(timeout, key); return ret; } catch (Exception e) { throw new ServiceException(e); } } /** * 修剪到指定范围的列表 */ public void ltrim(String key, int start, int end) { try (Jedis j = pool.getResource()) { j.ltrim(key, start, end); } catch (Exception e) { throw new ServiceException(e); } } // // --------------------------------Set相关命令 // 无序-------------------------------- // /** * SADD操作,添加Set类型数据. *

* o(N) 时间复杂度中的N表示操作的成员数量.
* 如果在插入的过程中,参数中有的成员已存在,该成员将被忽略,其它成员正常插入。
* 如果执行命令之前,此KEY并不存在,将以此Key创建新的Set * * @param key KEY值 * @param value 成员列表 * @return 本次操作实际插入的成员数量 */ public long sadd(String key, String value) { try (Jedis j = pool.getResource()) { long ret = j.sadd(key, value); return ret; } catch (Exception e) { throw new ServiceException(e); } } public long sadd(String key, String[] value) { try (Jedis j = pool.getResource()) { long ret = j.sadd(key, value); return ret; } catch (Exception e) { throw new ServiceException(e); } } public String save() { try (Jedis j = pool.getResource()) { String ret = j.save(); return ret; } catch (Exception e) { throw new ServiceException(e); } } /** * 获取Key所对应的Set集合里面的所有值 * * @param key KEY值 * @return Set集中的所有元素 * warning SMEMBERS 命令被用于处理一个大的集合键时, 它们可能会阻塞服务器达数秒之久。 */ public Set smembers(String key) { Set ret = null; try (Jedis j = pool.getResource()) { ret = j.smembers(key); } catch (Exception e) { ret = Collections.emptySet(); throw new ServiceException(e); } return ret; } public String spop(String key) { String ret = null; try (Jedis j = pool.getResource()) { ret = j.spop(key); return ret; } catch (Exception e) { throw new ServiceException(e); } } /** * 从集合移除元素(如果不存在则忽略) * * @return 返回成功移除的元素个数 */ public long srem(String key, String value) { try (Jedis j = pool.getResource()) { long ret = j.srem(key, value); return ret; } catch (Exception e) { throw new ServiceException(e); } } /** * 从集合移除元素(如果不存在则忽略) * * @return 返回成功移除的元素个数 */ public long srem(String key, String... value) { try (Jedis j = pool.getResource()) { long ret = j.srem(key, value); return ret; } catch (Exception e) { throw new ServiceException(e); } } /** * @return 返回集合的元素个数 */ public long scard(String key) { try (Jedis j = pool.getResource()) { long ret = j.scard(key); return ret; } catch (Exception e) { throw new ServiceException(e); } } /** * @return 如果成员元素是集合的成员,返回 1 。 如果成员元素不是集合的成员,或 key 不存在,返回 0 。 */ public boolean sismember(String key, String value) { try (Jedis j = pool.getResource()) { boolean ret = j.sismember(key, value); return ret; } catch (Exception e) { throw new ServiceException(e); } } /** * 将元素从一个集合移动到另一个集合 * * @return 成功移动,返回1 没有任何操作,返回0 */ public long smove(String srckey, String dstkey, String member) { try (Jedis j = pool.getResource()) { long ret = j.smove(srckey, dstkey, member); return ret; } catch (Exception e) { throw new ServiceException(e); } } // // ------------------------------Sorted set相关命令 // 有序--------------------------- // /** * 添加指定成员到Key对应的Set集合中. *

* 时间复杂度o(log(N)) N为Set集合中的元素个数
* 每一个成员都有一个分数值,如果指定成员存在,那么其分数就会被更新成最新的。
* 如果不存在,则会创建一个新的。 * * @param key KEY值 * @param score 分数值 * @param member 指定成员 * @return 返回添加到Set集合中的元素个数,不包括那种已存在只更新的分数的元素。 */ public long zadd(String key, double score, String member) { long ret = 0L; try (Jedis j = pool.getResource()) { ret = j.zadd(key, score, member); } catch (Exception e) { throw new ServiceException(e); } return ret; } public long zadd(String key, Map map) { long ret = 0L; try (Jedis j = pool.getResource()) { ret = j.zadd(key, map); } catch (Exception e) { throw new ServiceException(e); } return ret; } /** * 获取指定Key的Set集合中成员member的排名。 *

* 其中Set集合成员按score值递增(由小到大)排列。
* 注意:排名以0为底,也就是说score值最小的成员排名为0. * * @param key KEY值 * @param member 指定成员 * @return 如果member是key对应Set集合中的成员,则返回member的排名值。 */ public Long zrank(String key, String member) { try (Jedis j = pool.getResource()) { Long ret = j.zrank(key, member); return ret; } catch (Exception e) { throw new ServiceException(e); } } /** * 获取指定Key的Set集合中成员member的排名。 *

* 返回有序集 key 中成员 member 的排名。其中有序集成员按 score 值递减(从大到小)排序
* 注意:排名以0为底,也就是说score值最小的成员排名为0. * * @param key KEY值 * @param member 指定成员 * @return 如果member是key对应Set集合中的成员,则返回member的排名值。 */ public Long zrevrank(String key, String member) { try (Jedis j = pool.getResource()) { Long ret = j.zrevrank(key, member); return ret; } catch (Exception e) { throw new ServiceException(e); } } /** * 获取指定Key对应的Set集合中指定区间内的成员。 *

* 其中成员按Score值递增来排序,具有相同值的成员按字典序来排列.
* * @param key KEY值 * @param start 开始下标 rank * @param end 结束下标 rank * @return 如果指定区间有成员,则返回此区间的成员集合. */ public Set zrange(String key, long start, long end) { Set ret = null; try (Jedis j = pool.getResource()) { ret = j.zrange(key, start, end); } catch (Exception e) { ret = Collections.emptySet(); throw new ServiceException(e); } return ret; } /** * 获取指定Key对应的Set集合中指定区间内的成员。 *

* 其中成员按Score值递减来排序,具有相同值的成员按字典序来排列.
* * @param key KEY值 * @param start 开始下标 rank * @param end 结束下标 rank * @return 如果指定区间有成员,则返回此区间的成员集合. */ public Set zrevrange(String key, long start, long end) { Set ret = null; try (Jedis j = pool.getResource()) { ret = j.zrevrange(key, start, end); } catch (Exception e) { ret = Collections.emptySet(); throw new ServiceException(e); } return ret; } /** * 获取指定Key对应的Set集合中指定分数的成员。 * * @param key KEY值 * @param min 最小值 score * @param max 最大值 score * @return 如果指定区间有成员,则返回此区间的成员集合. */ public Set zrangeByScore(String key, double min, double max) { Set ret = null; try (Jedis j = pool.getResource()) { ret = j.zrangeByScore(key, min, max); } catch (Exception e) { ret = Collections.emptySet(); throw new ServiceException(e); } return ret; } /** * 返回指定分数区间的元素集合(包括min和max) 按照分数从大到小排序 * * @param key KEY值 * @param min 最小值 score * @param max 最大值 score * @return 如果指定区间有成员,则返回此区间的成员集合. */ public Set zrevrangeByScore(String key, double max, double min) { try (Jedis j = pool.getResource()) { Set ret = j.zrevrangeByScore(key, max, min); return ret; } catch (Exception e) { throw new ServiceException(e); } } /** * 返回指定排名区间的元素集合(按照score从大到小排序) * * @param key * @param start 开始下标 rank * @param end 结束下标 rank * @return 指定区间内,带有 score 值(可选)的有序集成员的列表。 */ public Set zrevrangeWithScore(String key, int start, int end) { try (Jedis j = pool.getResource()) { Set ret = j.zrevrangeWithScores(key, start, end); return ret; } catch (Exception e) { throw new ServiceException(e); } } /** * 返回指定排名区间的元素集合(按照score从小到大排序) * * @param key 主健值 * @param start 开始下标 rank * @param end 结束下标 rank */ public Set zrangeWithScore(String key, int start, int end) { try (Jedis j = pool.getResource()) { Set ret = j.zrangeWithScores(key, start, end); return ret; } catch (Exception e) { throw new ServiceException(e); } } /** * 返回指定排名区间的元素集合(按照score从小到大排序) * * @param key 主健值 * @param min 开始下标 rank * @param max 结束下标 rank */ public Set zrangeByScoreWithScores(String key, String min, String max) { try (Jedis j = pool.getResource()) { Set ret = j.zrangeByScoreWithScores(key, min, max); return ret; } catch (Exception e) { throw new ServiceException(e); } } /** * 返回指定排名区间的元素集合(按照score从大到小排序) * * @param min 开始下标 rank * @param max 结束下标 rank */ public Set zrevrangeByScoreWithScores(String key, String min, String max) { try (Jedis j = pool.getResource()) { Set ret = j.zrevrangeByScoreWithScores(key, min, max); return ret; } catch (Exception e) { throw new ServiceException(e); } } /** * 获取指定Key的Set集合中成员member的值。 *

* 备注:
* 如果 member 元素不是有序集 key 的成员,或 key 不存在,返回 nil * * @param key KEY值 * @param member 指定成员 * @return member 成员的 score 值,以字符串形式表示。 */ public long zscore(String key, String member) { try (Jedis j = pool.getResource()) { Double ret = j.zscore(key, member); if (ret == null) { return 0L; } return ret.longValue(); } catch (Exception e) { throw new ServiceException(e); } } /** * 获取指定Key的Set集合中成员member的值。 *

* 备注:
* 如果 member 元素不是有序集 key 的成员,或 key 不存在,返回 nil * * @param key KEY值 * @param member 指定成员 * @return member 成员的 score 值,以字符串形式表示。 */ public Double zscorex(String key, String member) { try (Jedis j = pool.getResource()) { Double ret = j.zscore(key, member); return ret; } catch (Exception e) { throw new ServiceException(e); } } /** * 返回key对应的Set集合中指定分数区间成员的个数。 *

* 注意: min <= 此分数值 <= max * * @param key KEY值 * @param min 分数值下限 * @param max 分数值上限 * @return 成员数量 */ public long zcount(String key, double min, double max) { long ret = 0L; try (Jedis j = pool.getResource()) { ret = j.zcount(key, min, max); } catch (Exception e) { throw new ServiceException(e); } return ret; } /** * 返回key对应的Set集合中成员的个数。 *

* * @param key KEY值 * @return 成员数量 */ public long zcount(String key) { long ret = 0L; try (Jedis j = pool.getResource()) { ret = j.zcount(key, "-inf", "+inf"); } catch (Exception e) { throw new ServiceException(e); } return ret; } /** * 删除指定Key对应的Set集合中指定的成员。 *

* * @param key KEY值 * @param members 指定的成员 * @return 返回被删除的元素的个数 */ public long zrem(String key, String... members) { try (Jedis j = pool.getResource()) { long ret = j.zrem(key, members); return ret; } catch (Exception e) { throw new ServiceException(e); } } /** * 移除排名在start和stop(包含start,stop)在内的所有元素 * * @return 返回移除元素的个数 */ public long zremrangeByRank(String key, int start, int stop) { try (Jedis j = pool.getResource()) { long ret = j.zremrangeByRank(key, start, stop); return ret; } catch (Exception e) { throw new ServiceException(e); } } /** * 移除分数在min和max(包含min,max)在内的所有元素 * * @return 返回移除元素的个数 */ public long zremrangeByScore(String key, double min, double max) { try (Jedis j = pool.getResource()) { long ret = j.zremrangeByScore(key, min, max); return ret; } catch (Exception e) { throw new ServiceException(e); } } /** * 返回key对应的Set集合中成员的个数。 *

* * @param key KEY值 * @return 成员数量 */ public long zcard(String key) { try (Jedis j = pool.getResource()) { long ret = j.zcard(key); return ret; } catch (Exception e) { throw new ServiceException(e); } } /** * 增加指定元素的分数 * * @return 指定元素新的分数值 */ public double zincrby(String key, double increment, String member) { try (Jedis j = pool.getResource()) { double ret = j.zincrby(key, increment, member); return ret; } catch (Exception e) { throw new ServiceException(e); } } /** * 执行脚本 * * @param script * @return 返回结果 */ public Object eval(String script) { try (Jedis j = pool.getResource()) { Object ret = j.eval(script); return ret; } catch (Exception e) { throw new ServiceException(e); } } // ------------------------------------------订阅/发布------------------------------------ /** * 将信息 message 发送到指定的频道 channel 。 *

* version=2.0.0 时间复杂度: O(N+M),其中 N 是频道channel 的订阅者数量,而 M * 则是使用模式订阅(subscribed patterns)的客户端的数量。 */ public long publish(String channel, String message) { try (Jedis j = pool.getResource()) { long ret = j.publish(channel, message); return ret; } catch (Exception e) { throw new ServiceException(e); } } /** * 订阅给定的一个或多个频道的信息。 *

* version=2.0.0 时间复杂度: O(N),其中 N 是订阅的频道的数量。 */ public void subscribe(JedisPubSub jedisPubSub, String... channel) { try (Jedis j = pool.getResource()) { j.subscribe(jedisPubSub, channel); } catch (Exception e) { throw new ServiceException(e); } } /** * 订阅一个或多个符合给定模式的频道。 *

* 每个模式以 * 作为匹配符,比如 it* 匹配所有以 it 开头的频道( it.news 、 it.blog 、 it.tweets 等等), * news.* 匹配所有以 news. 开头的频道( news.it 、 news.global.today 等等),诸如此类。 *

* version=2.0.0 时间复杂度: O(N), N 是订阅的模式的数量。 */ public void psubscribe(JedisPubSub jedisPubSub, String... channel) { try (Jedis j = pool.getResource()) { j.psubscribe(jedisPubSub, channel); } catch (Exception e) { throw new ServiceException(e); } } // // --------------------------------Server相关命令----------------------------- // /** * 清空当前数据库里的所有数据,这个命令永远不会失败,使用时请注意加小心。 */ public void flushDB() { try (Jedis j = pool.getResource()) { j.flushDB(); } catch (Exception e) { throw new ServiceException(e); } } /** * 获取当前数据库里的Keys的数量. * * @return Keys的数量 */ public long dbSize() { try (Jedis j = pool.getResource()) { long ret = j.dbSize(); return ret; } catch (Exception e) { throw new ServiceException(e); } } /** * 退出客户端 */ public String quit() { try (Jedis j = pool.getResource()) { String ret = j.quit(); return ret; } catch (Exception e) { throw new ServiceException(e); } } public void destory() { pool.destroy(); } /** * 可用版本:=2.6.0 时间复杂度: 这个命令在源实例上实际执行 DUMP 命令和 DEL 命令,在目标实例执行 RESTORE * 命令,查看以上命令的文档可以看到详细的复杂度说明。 key 数据在两个实例之间传输的复杂度为 O(N) 。 */ public String migrate(String host, int port, String key, int destinationDb, int timeout) { try (Jedis j = pool.getResource()) { String ret = j.migrate(host, port, key, destinationDb, timeout); return ret; } catch (Exception e) { throw new ServiceException(e); } } /** * @param fromHost 源redis ip * @param fromPort 源redis 端口 * @param fromIndex 源DB * @param toHost 目标redis ip * @param toPort 目标 redis 端口 * @param toIndex 目标DB * @param timeout 超时 毫秒 * @param list 要转移的键列表 */ public static void migrate(String fromHost, int fromPort, int fromIndex, String toHost, int toPort, int toIndex, int timeout, String... list) { long start = System.nanoTime(); logger.info("数据迁移开始 fromHost={},fromPort={},fromIndex={},toHost={},toPort={},toIndex={},timeout={},list={}", fromHost, fromPort, fromIndex, toHost, toPort, toIndex, timeout, list); Redis redis = new Redis(fromHost, fromPort, fromIndex); List ret = redis.migrate(toHost, toPort, toIndex, timeout, list); redis.destory(); logger.info("数据迁移完成 interval={},ret={}", (System.nanoTime() - start) / 1000000f, ret); } /** * 批量迁移键 */ public List migrate(String host, int port, int destinationDb, int timeout, String... list) { try (Jedis j = pool.getResource()) { Pipeline p = j.pipelined(); for (String key : list) { p.migrate(host, port, key, destinationDb, timeout); } List ret = p.syncAndReturnAll(); return ret; } catch (Exception e) { logger.error(e.getMessage(), e); } return null; } /** * 返回一个jedis实例,用于自己实现pipeline、multi、watch等 */ public Jedis getJedis() { return pool.getResource(); } // public void returnJedis(Jedis j) // { // // } public JedisPool getPool() { return pool; } public void setPool(JedisPool pool) { this.pool = pool; } } ================================================ FILE: gamioo-redis/src/main/java/io/gamioo/redis/RedisConstant.java ================================================ package io.gamioo.redis; /** * @author Allen */ public class RedisConstant { // Redis库的类型 /** * 全局库 */ public final static int REDIS_TYPE_GLOBAL = 1; /** * 本地库 */ public final static int REDIS_TYPE_LOCAL = 2; /** * 全局CDN */ public static final String KEY_GLOBAL_CDN = "globalcdn"; public static final String KEY_CDN = "cdn"; public static final String KEY_CDN_DIR = "cdn_dir"; public static final String HISTORY_CDN = "history_cdn"; public static final String HISTORY_CDN_URL = "url"; public static final String HISTORY_CDN_SUB_DOMAIN = "sub_domain"; public static final String HISTORY_CDN_ROOT_DOMAIN = "root_domain"; public static final String HISTORY_CDN_BACKUP_URL = "backup_url"; public static final String KEY_PLATFORMS = "platforms"; public static final String KEY_ONLINE = "online"; public static final String KEY_NOTICE_LIST = "notice"; public static final String KEY_GAME_LIST = "game_config"; /** * 用户信息 */ public static final String KEY_USER = "u"; /** * 用户ID,用于自增 */ public static final String KEY_USER_ID = "user_id"; /** * 用户名字和ID映射 */ public static final String KEY_USER_ID_NAME = "u3"; /** * 服务器信息 */ public static final String KEY_SERVER_INFO = "server_info"; /** * 样本信息 */ public static final String KEY_ROBOT = "robot"; /** * 机器人信息 */ public static final String KEY_ROBOT_INFO = "robot_info"; /** * 服务器 */ public static final String KEY_TARGET = "target"; // 频道 /** * 服务器增加频道 */ public static final String CHANNEL_ADD_SERVER = "channel-add-server"; public static final String CHANNEL_ADD_GLOBAL = "channel-add-global"; /** * 服务器删除频道 */ public static final String CHANNEL_REMOVE_SERVER = "channel-remove-server"; public static final String CHANNEL_REMOVE_GLOBAL = "channel-remove-global"; /** * 跨服服务器频道 */ public static final String CHANNEL_SERVER_LOBBY = "channel-lobby"; /** * 统计频道 */ public static final String CHANNEL_STATISTIC = "channel-stat"; /** * 监控频道 */ public static final String CHANNEL_MONITOR = "channel-monitor"; /** * 代码热更新 */ public static final String CHANNEL_HOT_CODE = "channel-hot-code"; /** * 平台更新 */ public static final String CHANNEL_PLATFORMS = "channel-platforms"; /** * 全局CDN频道 */ public static final String CHANNEL_GLOBAL_CDN = "channel-global-cdn"; /** * CDN频道 */ public static final String CHANNEL_CDN = "channel-cdn"; /** * 服务器频道 */ public static final String CHANNEL_SERVER = "channel-server-"; /** * 服务器在线人数频道 */ public static final String CHANNEL_SERVER_ONLINE = "channel-online"; /** * 服务器在线人数 */ public static final String SERVER_ONLINE = "server_online"; /** * 游戏类型在线人数 */ public static final String GAME_TYPE_ONLINE = "game_type_online"; /** * 消息类型和消息的分隔符 */ public static final String CHANNEL_SEPARATOR = "@"; public static String getServerChannel(String serverType, int serverId) { return RedisConstant.CHANNEL_SERVER + serverType + "-" + serverId; } } ================================================ FILE: gamioo-redis/src/main/java/io/gamioo/redis/zset/ZSetUtils.java ================================================ /* * Copyright 2019 wjybxx * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to iBn writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.redis.zset; import java.util.concurrent.ThreadLocalRandom; /** * ZSet工具类,提取公共代码和常量 * * @author wjybxx * @version 1.0 * date - 2019/11/26 */ public class ZSetUtils { /** * 成员id到成员分数的映射初始容量,减少不必要的扩容。 */ public static final int INIT_CAPACITY = 128; /** * 跳表允许最大层级 */ public static final int ZSKIPLIST_MAXLEVEL = 32; /** * 跳表升层概率 */ private static final float ZSKIPLIST_P = 0.25f; /** * 转换起始排名 * * @param start 请求参数中的起始排名(0-based) * @param zslLength 跳表的长度 * @return 有效起始排名 */ public static int convertStartRank(int start, int zslLength) { if (start < 0) { start += zslLength; } if (start < 0) { start = 0; } return start; } /** * 转换截止排名 * * @param end 请求参数中的截止排名(0-based) * @param zslLength 跳表的长度 * @return 有效截止排名 */ public static int convertEndRank(int end, int zslLength) { if (end < 0) { end += zslLength; } if (end >= zslLength) { end = zslLength - 1; } return end; } /** * 判断排名区间是否为空 * * @param start 转换后的起始排名(0-based) * @param end 转换后的截止排名(0-based) * @param zslLength 跳表长度 * @return true/false */ public static boolean isRankRangeEmpty(final int start, final int end, final int zslLength) { /* Invariant: start >= 0, so this test will be true when end < 0. * The range is empty when start > end or start >= length. */ return start > end || start >= zslLength; } /** * 返回一个随机的层级分配给即将插入的节点。 * 返回的层级值在 1 和 ZSKIPLIST_MAXLEVEL 之间(包含两者)。 * 具有类似幂次定律的分布,越高level返回的可能性更小。 *

* Returns a random level for the new skiplist node we are going to create. * The return value of this function is between 1 and ZSKIPLIST_MAXLEVEL * (both inclusive), with a powerlaw-alike distribution where higher * levels are less likely to be returned. * * @return level */ public static int zslRandomLevel() { int level = 1; while (level < ZSKIPLIST_MAXLEVEL && ThreadLocalRandom.current().nextFloat() < ZSKIPLIST_P) { level++; } return level; } /** * 释放update引用的对象 * * @param update 更新数组 * @param realLength 实际长度 */ public static void releaseUpdate(Object[] update, int realLength) { for (int index = 0; index < realLength; index++) { update[index] = null; } } /** * 重置rank中的数据 * * @param rank 范围 * @param realLength 实际长度 */ public static void releaseRank(int[] rank, int realLength) { for (int index = 0; index < realLength; index++) { rank[index] = 0; } } } ================================================ FILE: gamioo-redis/src/main/java/io/gamioo/redis/zset/generic/Entry.java ================================================ /* * Copyright 2019 wjybxx * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to iBn writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.redis.zset.generic; /** * zset中单个成员信息 */ public interface Entry { K getMember(); S getScore(); } ================================================ FILE: gamioo-redis/src/main/java/io/gamioo/redis/zset/generic/GenericZSet.java ================================================ /* * Copyright 2019 wjybxx * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to iBn writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.redis.zset.generic; import io.gamioo.redis.zset.ZSetUtils; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.concurrent.NotThreadSafe; import java.util.*; import static io.gamioo.redis.zset.ZSetUtils.ZSKIPLIST_MAXLEVEL; /** * key和score均为泛型类型的sorted set - 参考redis的zset实现 * 排序规则 * 有序集合里面的成员是不能重复的,都是唯一的,但是,不同成员间有可能有相同的分数。 * 当多个成员有相同的分数时,它们将按照键排序。 * 即:分数作为第一排序条件,键作为第二排序条件,当分数相同时,比较键的大小。 *

* NOTE: * 1. ZSET中的排名从0开始(提供给用户的接口,排名都从0开始) * 2. ZSET使用键的compare结果判断两个键是否相等,而不是equals方法,因此必须保证键不同时compare结果一定不为0。 * 3. 又由于key需要存放于{@link HashMap}中,因此“相同”的key必须有相同的hashCode,且equals方法返回true。 * 手动加粗:key的关键属性最好是number或string且是final的 *

* 4. 我们允许zset中的成员是降序排列的-{@link ScoreHandler}决定,可以更好的支持根据score降序的排行榜, * 而不是强迫你总是调用反转系列接口{@code zrev...},那样的设计不符合人的正常思维,就很容易出错。 *

* 5. 我们修改了redis中根据min和max查找和删除成员的接口,修改为start和end,当根据score范围查找或删除元素时,并不要求start小于等于end,我们会处理它们的大小关系。
* Q: 为什么要这么改动呢?
* A: 举个栗子:假如ScoreHandler比较两个long类型的score是逆序的,现在要删除排行榜中 1-10000分的成员,如果方法告诉你要传入的的是min和max, * 你会很自然的传入想到 (1,10000) 而不是 (10000,1)。因此,如果接口不做调整,这个接口就太反人类了,谁用都得错。 *

* 6. score泛型化以后,有一些注意事项,请查看{@link ScoreHandler}中关于score的注意事项。 * *

* 这里只实现了redis zset中的常用的接口,扩展不是太麻烦,可以自己根据需要实现。 * * @param the type of member * @param the type of score * @author wjybxx * @version 1.0 * date - 2019/11/4 */ @NotThreadSafe public class GenericZSet implements Iterable> { /** * member -> score */ private final Map dict = new HashMap<>(ZSetUtils.INIT_CAPACITY); private final SkipList zsl; private GenericZSet(Comparator objComparator, ScoreHandler scoreHandler) { this.zsl = new SkipList<>(objComparator, scoreHandler); } /** * 创建一个键为string类型的zset * * @param scoreHandler 分数处理器 * @param score类型 * @return zset */ public static GenericZSet newStringKeyZSet(ScoreHandler scoreHandler) { return new GenericZSet<>(String::compareTo, scoreHandler); } /** * 创建一个键为long类型的zset * * @param scoreHandler 分数处理器 * @param score类型 * @return zset */ public static GenericZSet newLongKeyZSet(ScoreHandler scoreHandler) { return new GenericZSet<>(Long::compareTo, scoreHandler); } /** * 创建一个键为int类型的zset * * @param scoreHandler 分数处理器 * @param score类型 * @return zset */ public static GenericZSet newIntKeyZSet(ScoreHandler scoreHandler) { return new GenericZSet<>(Integer::compareTo, scoreHandler); } /** * 创建一个自定义键类型的zset * * @param keyComparator 键比较器,当score比较结果相等时,比较key - 注意:比较结果必须与key对象的状态改变无关。 * 请仔细阅读类文档中的注意事项。 * @param scoreHandler 分数处理器 * @param 键的类型 * @param score的类型 * @return zset */ public static GenericZSet newGenericKeyZSet(Comparator keyComparator, ScoreHandler scoreHandler) { return new GenericZSet<>(keyComparator, scoreHandler); } // -------------------------------------------------------- insert ----------------------------------------------- /** * 往有序集合中新增一个成员。 * 如果指定添加的成员已经是有序集合里面的成员,则会更新成员的分数(score)并更新到正确的排序位置。 * * @param score 数据的评分 * @param member 成员id */ public void zadd(final S score, @Nonnull final K member) { Objects.requireNonNull(score); Objects.requireNonNull(member); final S oldScore = dict.put(member, score); if (oldScore != null) { // Q: 为何不再判断分数相等? // A: 这里假定分数相等的情况很少出现,可减少大量无用的判断 zsl.zslDelete(oldScore, member); } zsl.zslInsert(score, member); } /** * 往有序集合中新增一个成员。当且仅当该成员不在有序集合时才添加。 * * @param score 数据的评分 * @param member 成员id * @return 添加成功则返回true,否则返回false。 */ public boolean zaddnx(final S score, @Nonnull final K member) { Objects.requireNonNull(score); Objects.requireNonNull(member); final S oldScore = dict.putIfAbsent(member, score); if (oldScore == null) { zsl.zslInsert(score, member); return true; } return false; } /** * 为有序集的成员member的score值加上增量increment,并更新到正确的排序位置。 * 如果有序集中不存在member,就在有序集中添加一个member,score是increment(就好像它之前的score是0) * * @param increment 自定义增量 * @param member 成员id * @return 更新后的值 */ public S zincrby(S increment, @Nonnull K member) { Objects.requireNonNull(increment); Objects.requireNonNull(member); final S oldScore = dict.get(member); final S score = oldScore == null ? increment : zsl.sum(oldScore, increment); zadd(score, member); return score; } /** * 为有序集的成员member的score值加上增量increment,并更新到正确的排序位置。 * 如果有序集中不存在member,则放弃更新并返回null。 * * @param increment 自定义增量 * @param member 成员id * @return 更新后的值,如果更新失败,则返回null。 */ public S zincrbyxx(S increment, @Nonnull K member) { Objects.requireNonNull(increment); Objects.requireNonNull(member); final S oldScore = dict.get(member); if (oldScore == null) { return null; } final S score = zsl.sum(oldScore, increment); zadd(score, member); return score; } // -------------------------------------------------------- remove ----------------------------------------------- /** * 删除指定成员 * * @param member 成员id * @return 如果成员存在,则返回对应的score,否则返回null。 */ public S zrem(@Nonnull K member) { Objects.requireNonNull(member); final S oldScore = dict.remove(member); if (oldScore != null) { zsl.zslDelete(oldScore, member); return oldScore; } else { return null; } } // region 通过score删除成员 /** * 移除zset中所有score值介于start和end之间(包括等于start或end)的成员 * * @param start 起始分数 inclusive * @param end 截止分数 inclusive * @return 删除的成员数目 */ public int zremrangeByScore(S start, S end) { return zremrangeByScore(zsl.newRangeSpec(start, end)); } /** * 移除zset中所有score值在范围区间的成员 * * @param spec score范围区间 * @return 删除的成员数目 */ private int zremrangeByScore(@Nonnull ScoreRangeSpec spec) { return zremrangeByScore(zsl.newRangeSpec(spec)); } /** * 移除zset中所有score值在范围区间的成员 * * @param spec score范围区间 * @return 删除的成员数目 */ private int zremrangeByScore(@Nonnull ZScoreRangeSpec spec) { return zsl.zslDeleteRangeByScore(spec, dict); } // endregion // region 通过排名删除成员 /** * 删除并返回有序集合中的第一个成员。 * - 不使用min和max,是因为score的比较方式是用户自定义的。 * * @return 如果不存在,则返回null */ @Nullable public Entry zpopFirst() { return zremByRank(0); } /** * 删除并返回有序集合中的最后一个成员。 * - 不使用min和max,是因为score的比较方式是用户自定义的。 * * @return 如果不存在,则返回null */ @Nullable public Entry zpopLast() { return zremByRank(zsl.length() - 1); } /** * 删除指定排名的成员 * * @param rank 排名 0-based * @return 删除成功则返回该排名对应的数据,否则返回null */ @Nullable public Entry zremByRank(int rank) { if (rank < 0 || rank >= zsl.length()) { return null; } final SkipListNode delete = zsl.zslDeleteByRank(rank + 1, dict); assert null != delete; return new ZSetEntry<>(delete.obj, delete.score); } /** * 删除指定排名范围的全部成员,start和end都是从0开始的。 * 排名0表示分数最小的成员。 * start和end都可以是负数,此时它们表示从最高排名成员开始的偏移量,eg: -1表示最高排名的成员, -2表示第二高分的成员,以此类推。 *

* Time complexity: O(log(N))+O(M) with N being the number of elements in the sorted set * and M the number of elements removed by the operation * * @param start 起始排名 * @param end 截止排名 * @return 删除的成员数目 */ public int zremrangeByRank(int start, int end) { final int zslLength = zsl.length(); start = ZSetUtils.convertStartRank(start, zslLength); end = ZSetUtils.convertEndRank(end, zslLength); if (ZSetUtils.isRankRangeEmpty(start, end, zslLength)) { return 0; } return zsl.zslDeleteRangeByRank(start + 1, end + 1, dict); } // endregion // region 限制成员数量 /** * 删除zset中尾部多余的成员,将zset中的成员数量限制到count之内。 * 保留前面的count个数成员 * * @param count 剩余数量限制 * @return 删除的成员数量 */ public int zlimit(int count) { if (zsl.length() <= count) { return 0; } return zsl.zslDeleteRangeByRank(count + 1, zsl.length(), dict); } /** * 删除zset中头部多余的成员,将zset中的成员数量限制到count之内。 * - 保留后面的count个数成员 * * @param count 剩余数量限制 * @return 删除的成员数量 */ public int zrevlimit(int count) { if (zsl.length() <= count) { return 0; } return zsl.zslDeleteRangeByRank(1, zsl.length() - count, dict); } // endregion // -------------------------------------------------------- query ----------------------------------------------- /** * 返回有序集成员member的score值。 * 如果member成员不是有序集的成员,返回null - 这里返回任意的基础值都是不合理的,因此必须返回null。 * * @param member 成员id * @return score */ public S zscore(@Nonnull K member) { Objects.requireNonNull(member); return dict.get(member); } /** * 返回有序集中成员member的排名。 *

* Time complexity: O(log(N)) *

* 与redis的区别:我们使用-1表示成员不存在,而不是返回null。 * * @param member 成员id * @return 如果存在该成员,则返回该成员的排名(0-based),否则返回-1 */ public int zrank(@Nonnull K member) { Objects.requireNonNull(member); final S score = dict.get(member); if (score == null) { return -1; } // 0 < zslGetRank <= size return zsl.zslGetRank(score, member) - 1; } /** * 返回有序集中成员member的逆序排名。 *

* Time complexity: O(log(N)) *

* 与redis的区别:我们使用-1表示成员不存在,而不是返回null。 * * @param member 成员id * @return 如果存在该成员,则返回该成员的排名(0-based),否则返回-1 */ public int zrevrank(@Nonnull K member) { final S score = dict.get(member); if (score == null) { return -1; } // 0 < zslGetRank <= size return zsl.length() - zsl.zslGetRank(score, member); } /** * 获取指定排名的成员数据。 * * @param rank 排名 0-based * @return memver,如果不存在,则返回null */ public Entry zmemberByRank(int rank) { if (rank < 0 || rank >= zsl.length()) { return null; } final SkipListNode node = zsl.zslGetElementByRank(rank + 1); assert null != node; return new ZSetEntry<>(node.obj, node.score); } /** * 获取指定逆序排名的成员数据。 * * @param rank 排名 0-based * @return memver,如果不存在,则返回null */ public Entry zrevmemberByRank(int rank) { if (rank < 0 || rank >= zsl.length()) { return null; } final SkipListNode node = zsl.zslGetElementByRank(zsl.length() - rank); assert null != node; return new ZSetEntry<>(node.obj, node.score); } // region 通过分数查询 /** * 返回有序集合中的分数在start和end之间的所有成员(包括分数等于start或者end的成员)。 * * @param start 起始分数 inclusive * @param end 截止分数 inclusive * @return memberInfo */ public List> zrangeByScore(S start, S end) { return zrangeByScoreWithOptions(zsl.newRangeSpec(start, end), 0, -1, false); } /** * 返回有序集合中的分数在指定范围区间的所有成员。 * * @param spec 范围描述信息 * @return memberInfo */ public List> zrangeByScore(ScoreRangeSpec spec) { return zrangeByScoreWithOptions(zsl.newRangeSpec(spec), 0, -1, false); } /** * 返回有序集合中的分数在start和end之间的所有成员(包括分数等于start或者end的成员),返回的成员按照逆序排列。 * * @param start 起始分数 inclusive * @param end 截止分数 inclusive * @return memberInfo */ public List> zrevrangeByScore(final S start, final S end) { return zrangeByScoreWithOptions(zsl.newRangeSpec(start, end), 0, -1, true); } /** * 返回有序集合中的分数在指定范围之间的所有成员,返回的成员按照逆序排列。 * * @param rangeSpec score范围区间 * @return 删除的成员数目 */ public List> zrevrangeByScore(ScoreRangeSpec rangeSpec) { return zrangeByScoreWithOptions(zsl.newRangeSpec(rangeSpec), 0, -1, true); } /** * 返回zset中指定分数区间内的成员,并按照指定顺序返回 * * @param rangeSpec score范围描述信息 * @param offset 偏移量(用于分页) 大于等于0 * @param limit 返回的成员数量(用于分页) 小于0表示不限制 * @param reverse 是否逆序 * @return memberInfo */ public List> zrangeByScoreWithOptions(final ScoreRangeSpec rangeSpec, int offset, int limit, boolean reverse) { return zrangeByScoreWithOptions(zsl.newRangeSpec(rangeSpec), offset, limit, reverse); } /** * 返回zset中指定分数区间内的成员,并按照指定顺序返回 * * @param range score范围描述信息 * @param offset 偏移量(用于分页) 大于等于0 * @param limit 返回的成员数量(用于分页) 小于0表示不限制 * @param reverse 是否逆序 * @return memberInfo */ private List> zrangeByScoreWithOptions(final ZScoreRangeSpec range, int offset, int limit, boolean reverse) { if (offset < 0) { throw new IllegalArgumentException("offset" + ": " + offset + " (expected: >= 0)"); } SkipListNode listNode; /* If reversed, get the last node in range as starting point. */ if (reverse) { listNode = zsl.zslLastInRange(range); } else { listNode = zsl.zslFirstInRange(range); } /* No "first" element in the specified interval. */ if (listNode == null) { return new ArrayList<>(); } /* If there is an offset, just traverse the number of elements without * checking the score because that is done in the next loop. */ while (listNode != null && offset-- != 0) { if (reverse) { listNode = listNode.backward; } else { listNode = listNode.levelInfo[0].forward; } } final List> result = new ArrayList<>(); /* 这里使用 != 0 判断,当limit小于0时,表示不限制 */ while (listNode != null && limit-- != 0) { /* Abort when the node is no longer in range. */ if (reverse) { if (!zsl.zslValueGteMin(listNode.score, range)) { break; } } else { if (!zsl.zslValueLteMax(listNode.score, range)) { break; } } result.add(new ZSetEntry<>(listNode.obj, listNode.score)); /* Move to next node */ if (reverse) { listNode = listNode.backward; } else { listNode = listNode.levelInfo[0].forward; } } return result; } // endregion // region 通过排名查询 /** * 查询指定排名区间的成员信息 * * @param start 起始排名(0-based) inclusive * @param end 截止排名(0-based) inclusive * @return memberInfo */ public List> zrangeByRank(int start, int end) { return zrangeByRankInternal(start, end, false); } /** * 查询指定逆序排名区间的成员信息 * * @param start 起始排名(0-based) inclusive * @param end 截止排名(0-based) inclusive * @return memberInfo */ public List> zrevrangeByRank(int start, int end) { return zrangeByRankInternal(start, end, true); } /** * 查询指定排名区间的成员id和分数,start和end都是从0开始的。 * * @param start 起始排名(0-based) inclusive * @param end 截止排名(0-based) inclusive * @param reverse 是否逆序返回 * @return memberInfo */ private List> zrangeByRankInternal(int start, int end, boolean reverse) { final int zslLength = zsl.length(); start = ZSetUtils.convertStartRank(start, zslLength); end = ZSetUtils.convertEndRank(end, zslLength); if (ZSetUtils.isRankRangeEmpty(start, end, zslLength)) { return new ArrayList<>(); } int rangeLen = end - start + 1; SkipListNode listNode; /* start >= 0,大于0表示需要进行调整 */ /* Check if starting point is trivial, before doing log(N) lookup. */ if (reverse) { listNode = start > 0 ? zsl.zslGetElementByRank(zslLength - start) : zsl.tail; } else { listNode = start > 0 ? zsl.zslGetElementByRank(start + 1) : zsl.header.levelInfo[0].forward; } final List> result = new ArrayList<>(rangeLen); while (rangeLen-- > 0 && listNode != null) { result.add(new ZSetEntry<>(listNode.obj, listNode.score)); listNode = reverse ? listNode.backward : listNode.levelInfo[0].forward; } return result; } // endregion // region 统计分数人数 /** * 返回有序集key中,score值在指定区间(包括score值等于start或end)的成员 * * @param start 起始分数 * @param end 截止分数 * @return 分数区间段内的成员数量 */ public int zcount(S start, S end) { return zcountInternal(zsl.newRangeSpec(start, end)); } /** * 返回有序集key中,score值在指定区间的成员 * * @param rangeSpec score区间描述信息 * @return 分数区间段内的成员数量 */ public int zcount(ScoreRangeSpec rangeSpec) { return zcountInternal(zsl.newRangeSpec(rangeSpec)); } /** * 返回有序集key中,score值在指定区间的成员 * * @param range score区间描述信息 * @return 分数区间段内的成员数量 */ private int zcountInternal(final ZScoreRangeSpec range) { final SkipListNode firstNodeInRange = zsl.zslFirstInRange(range); if (firstNodeInRange != null) { final int firstNodeRank = zsl.zslGetRank(firstNodeInRange.score, firstNodeInRange.obj); /* 如果firstNodeInRange不为null,那么lastNode也一定不为null(最坏的情况下firstNode就是lastNode) */ final SkipListNode lastNodeInRange = zsl.zslLastInRange(range); assert lastNodeInRange != null; final int lastNodeRank = zsl.zslGetRank(lastNodeInRange.score, lastNodeInRange.obj); return lastNodeRank - firstNodeRank + 1; } return 0; } /** * @return zset中的成员数量 */ public int zcard() { return zsl.length(); } // endregion // region 迭代 /** * 迭代有序集中的所有元素 * * @return iterator */ @Nonnull public Iterator> zscan() { return zscan(0); } /** * 从指定偏移量开始迭代有序集中的元素 * * @param offset 偏移量,如果小于等于0,则等价于{@link #zscan()} * @return iterator */ @Nonnull public Iterator> zscan(int offset) { if (offset <= 0) { return new ZSetItr(zsl.header.directForward()); } if (offset >= zsl.length()) { return new ZSetItr(null); } return new ZSetItr(zsl.zslGetElementByRank(offset + 1)); } @Nonnull @Override public Iterator> iterator() { return zscan(0); } /** * {@link Iterator#next()}总是返回相同的{@link Entry}对象,外部在迭代时不可以保持其引用。 * 改迭代器是为了避免创建大量的{@link Entry}对象。 * * @param offset 偏移量 * @return 组合 */ @Nonnull public Iterator> fastzscan(int offset) { if (offset <= 0) { return new FastZSetItr(zsl.header.directForward()); } if (offset >= zsl.length()) { return new FastZSetItr(null); } return new FastZSetItr(zsl.zslGetElementByRank(offset + 1)); } /** * {@link Iterator#next()}总是返回相同的{@link Entry}对象,外部在迭代时不可以保持其引用。 * 改迭代器是为了避免创建大量的{@link Entry}对象。 * * @return 组合 */ @Nonnull public Iterator> fastIterator() { return fastzscan(0); } // endregion /** * @return zset中当前的成员信息,用于debug */ public String dump() { return zsl.dump(); } // ------------------------------------------------------- 内部实现 ---------------------------------------- /** * 跳表 * 注意:跳表的排名是从1开始的。 * * @author wjybxx * @version 1.0 * date - 2019/11/4 */ private static class SkipList { /** * 更新节点使用的缓存 - 避免频繁的申请空间 */ @SuppressWarnings("unchecked") private final SkipListNode[] updateCache = new SkipListNode[ZSKIPLIST_MAXLEVEL]; private final int[] rankCache = new int[ZSKIPLIST_MAXLEVEL]; private final Comparator objComparator; private final ScoreHandler scoreHandler; /** * 修改次数 - 防止错误的迭代 */ private int modCount = 0; /** * 跳表头结点 - 哨兵 * 1. 可以简化判定逻辑 * 2. 恰好可以使得rank从1开始 */ private final SkipListNode header; /** * 跳表尾节点 */ private SkipListNode tail; /** * 跳表成员个数 * 注意:head头指针不包含在length计数中。 */ private int length = 0; /** * level表示SkipList的总层数,即所有节点层数的最大值。 */ private int level = 1; SkipList(Comparator objComparator, ScoreHandler scoreHandler) { this.objComparator = objComparator; this.scoreHandler = scoreHandler; this.header = zslCreateNode(ZSKIPLIST_MAXLEVEL, null, null); } /** * 插入一个新的节点到跳表。 * 这里假定成员已经不存在(直到调用方执行该方法)。 *

* zslInsert a new node in the skiplist. Assumes the element does not already * exist (up to the caller to enforce that). *

         *             header                    newNode
         *               _                                                 _
         * level - 1    |_| pre                                           |_|
         *  |           |_| pre                    _                      |_|
         *  |           |_| pre  _                |_|                     |_|
         *  |           |_|  ↓  |_| pre  _        |_|      _              |_|
         *  |           |_|     |_|  ↓  |_| pre   |_|     |_|             |_|
         *  |           |_|     |_|     |_| pre   |_|     |_|      _      |_|
         *  |           |_|     |_|     |_| pre   |_|     |_|     |_|     |_|
         *  0           |0|     |1|     |2| pre   |_|     |3|     |4|     |5|
         * 
* * @param score 分数 * @param obj obj 分数对应的成员id */ @SuppressWarnings("UnusedReturnValue") SkipListNode zslInsert(S score, K obj) { // 新节点的level final int level = ZSetUtils.zslRandomLevel(); // update - 需要更新后继节点的Node,新节点各层的前驱节点 // 1. 分数小的节点 // 2. 分数相同但id小的节点(分数相同时根据数据排序) // rank - 新节点各层前驱的当前排名 // 这里不必创建一个ZSKIPLIST_MAXLEVEL长度的数组,它取决于插入节点后的新高度,你在别处看见的代码会造成大量的空间浪费,增加GC压力。 // 如果创建的都是ZSKIPLIST_MAXLEVEL长度的数组,那么应该实现缓存 final SkipListNode[] update = updateCache; final int[] rank = rankCache; final int realLength = Math.max(level, this.level); try { // preNode - 新插入节点的前驱节点 SkipListNode preNode = header; for (int i = this.level - 1; i >= 0; i--) { /* store rank that is crossed to reach the insert position */ if (i == (this.level - 1)) { // 起始点,也就是head,它的排名就是0 rank[i] = 0; } else { // 由于是回溯降级继续遍历,因此其初始排名是前一次遍历的排名 rank[i] = rank[i + 1]; } while (preNode.levelInfo[i].forward != null && compareScoreAndObj(preNode.levelInfo[i].forward, score, obj) < 0) { // preNode的后继节点仍然小于要插入的节点,需要继续前进,同时累计排名 rank[i] += preNode.levelInfo[i].span; preNode = preNode.levelInfo[i].forward; } // 这是要插入节点的第i层的前驱节点,此时触发降级 update[i] = preNode; } if (level > this.level) { /* 新节点的层级大于当前层级,那么高出来的层级导致需要更新head,且排名和跨度是固定的 */ for (int i = this.level; i < level; i++) { rank[i] = 0; update[i] = this.header; update[i].levelInfo[i].span = this.length; } this.level = level; } /* 由于我们允许的重复score,并且zslInsert(该方法)的调用者在插入前必须测试要插入的member是否已经在hash表中。 * 因此我们假设key(obj)尚未被插入,并且重复插入score的情况永远不会发生。*/ /* we assume the key is not already inside, since we allow duplicated * scores, and the re-insertion of score and redis object should never * happen since the caller of zslInsert() should test in the hash table * if the element is already inside or not.*/ final SkipListNode newNode = zslCreateNode(level, score, obj); /* 这些节点的高度小于等于新插入的节点的高度,需要更新指针。此外它们当前的跨度被拆分了两部分,需要重新计算。 */ for (int i = 0; i < level; i++) { /* 链接新插入的节点 */ newNode.levelInfo[i].forward = update[i].levelInfo[i].forward; update[i].levelInfo[i].forward = newNode; /* rank[0] 是新节点的直接前驱的排名,每一层都有一个前驱,可以通过彼此的排名计算跨度 */ /* 计算新插入节点的跨度 和 重新计算所有前驱节点的跨度,之前的跨度被拆分为了两份*/ /* update span covered by update[i] as newNode is inserted here */ newNode.levelInfo[i].span = update[i].levelInfo[i].span - (rank[0] - rank[i]); update[i].levelInfo[i].span = (rank[0] - rank[i]) + 1; } /* 这些节点高于新插入的节点,它们的跨度可以简单的+1 */ /* increment span for untouched levels */ for (int i = level; i < this.level; i++) { update[i].levelInfo[i].span++; } /* 设置新节点的前向节点(回溯节点) - 这里不包含header,一定注意 */ newNode.backward = (update[0] == this.header) ? null : update[0]; /* 设置新节点的后向节点 */ if (newNode.levelInfo[0].forward != null) { newNode.levelInfo[0].forward.backward = newNode; } else { this.tail = newNode; } this.length++; this.modCount++; return newNode; } finally { ZSetUtils.releaseUpdate(update, realLength); ZSetUtils.releaseRank(rank, realLength); } } /** * Delete an element with matching score/object from the skiplist. * * @param score 分数用于快速定位节点 * @param obj 用于确定节点是否是对应的数据节点 */ @SuppressWarnings("UnusedReturnValue") boolean zslDelete(S score, K obj) { // update - 需要更新后继节点的Node // 1. 分数小的节点 // 2. 分数相同但id小的节点(分数相同时根据数据排序) final SkipListNode[] update = updateCache; final int realLength = this.level; try { SkipListNode preNode = this.header; for (int i = this.level - 1; i >= 0; i--) { while (preNode.levelInfo[i].forward != null && compareScoreAndObj(preNode.levelInfo[i].forward, score, obj) < 0) { // preNode的后继节点仍然小于要删除的节点,需要继续前进 preNode = preNode.levelInfo[i].forward; } // 这是目标节点第i层的可能前驱节点 update[i] = preNode; } /* 由于可能多个节点拥有相同的分数,因此必须同时比较score和object */ /* We may have multiple elements with the same score, what we need * is to find the element with both the right score and object. */ final SkipListNode targetNode = preNode.levelInfo[0].forward; if (targetNode != null && scoreEquals(targetNode.score, score) && objEquals(targetNode.obj, obj)) { zslDeleteNode(targetNode, update); return true; } /* not found */ return false; } finally { ZSetUtils.releaseUpdate(update, realLength); } } /** * Internal function used by zslDelete, zslDeleteByScore and zslDeleteByRank * * @param deleteNode 要删除的节点 * @param update 可能要更新的节点们 */ private void zslDeleteNode(final SkipListNode deleteNode, final SkipListNode[] update) { for (int i = 0; i < this.level; i++) { if (update[i].levelInfo[i].forward == deleteNode) { // 这些节点的高度小于等于要删除的节点,需要合并两个跨度 update[i].levelInfo[i].span += deleteNode.levelInfo[i].span - 1; update[i].levelInfo[i].forward = deleteNode.levelInfo[i].forward; } else { // 这些节点的高度高于要删除的节点,它们的跨度可以简单的 -1 update[i].levelInfo[i].span--; } } if (deleteNode.levelInfo[0].forward != null) { // 要删除的节点有后继节点 deleteNode.levelInfo[0].forward.backward = deleteNode.backward; } else { // 要删除的节点是tail节点 this.tail = deleteNode.backward; } // 如果删除的节点是最高等级的节点,则检查是否需要降级 if (deleteNode.levelInfo.length == this.level) { while (this.level > 1 && this.header.levelInfo[this.level - 1].forward == null) { // 如果最高层没有后继节点,则降级 this.level--; } } this.length--; this.modCount++; } /** * 判断zset中的数据所属的范围是否和指定range存在交集(intersection)。 * 它不代表zset存在指定范围内的数据。 * Returns if there is a part of the zset is in range. *
         *                         ZSet
         *              min ____________________ max
         *                 |____________________|
         *   min ______________ max  min _____________
         *      |______________|        |_____________|
         *          Range                   Range
         * 
* * @param range 范围描述信息 * @return true/false */ @SuppressWarnings("BooleanMethodIsAlwaysInverted") boolean zslIsInRange(ZScoreRangeSpec range) { if (isScoreRangeEmpty(range)) { // 传进来的范围为空 return false; } if (this.tail == null || !zslValueGteMin(this.tail.score, range)) { // 列表有序,按照从score小到大,如果尾部节点数据小于最小值,那么一定不在区间范围内 return false; } final SkipListNode firstNode = this.header.levelInfo[0].forward; if (firstNode == null || !zslValueLteMax(firstNode.score, range)) { // 列表有序,按照从score小到大,如果首部节点数据大于最大值,那么一定不在范围内 return false; } return true; } /** * 测试score范围信息是否为空(无效) * * @param range 范围描述信息 * @return true/false */ private boolean isScoreRangeEmpty(ZScoreRangeSpec range) { // 这里和redis有所区别,这里min一定小于等于max return scoreEquals(range.min, range.max) && (range.minex || range.maxex); } /** * 找出第一个在指定范围内的节点。如果没有符合的节点,则返回null。 *

* Find the first node that is contained in the specified range. * Returns NULL when no element is contained in the range. * * @param range 范围描述符 * @return 不存在返回null */ @Nullable SkipListNode zslFirstInRange(ZScoreRangeSpec range) { /* zset数据范围与指定范围没有交集,可提前返回,减少不必要的遍历 */ /* If everything is out of range, return early. */ if (!zslIsInRange(range)) { return null; } SkipListNode lastNodeLtMin = this.header; for (int i = this.level - 1; i >= 0; i--) { /* 前进直到出现后继节点大于等于指定最小值的节点 */ /* Go forward while *OUT* of range. */ while (lastNodeLtMin.levelInfo[i].forward != null && !zslValueGteMin(lastNodeLtMin.levelInfo[i].forward.score, range)) { // 如果当前节点的后继节点仍然小于指定范围的最小值,则继续前进 lastNodeLtMin = lastNodeLtMin.levelInfo[i].forward; } } /* 这里的上下文表明了,一定存在一个节点的值大于等于指定范围的最小值,因此下一个节点一定不为null */ /* This is an inner range, so the next node cannot be NULL. */ final SkipListNode firstNodeGteMin = lastNodeLtMin.levelInfo[0].forward; assert firstNodeGteMin != null; /* 如果该节点的数据大于max,则不存在再范围内的节点 */ /* Check if score <= max. */ if (!zslValueLteMax(firstNodeGteMin.score, range)) { return null; } return firstNodeGteMin; } /** * 找出最后一个在指定范围内的节点。如果没有符合的节点,则返回null。 *

* Find the last node that is contained in the specified range. * Returns NULL when no element is contained in the range. * * @param range 范围描述信息 * @return 不存在返回null */ @Nullable SkipListNode zslLastInRange(ZScoreRangeSpec range) { /* zset数据范围与指定范围没有交集,可提前返回,减少不必要的遍历 */ /* If everything is out of range, return early. */ if (!zslIsInRange(range)) { return null; } SkipListNode lastNodeLteMax = this.header; for (int i = this.level - 1; i >= 0; i--) { /* Go forward while *IN* range. */ while (lastNodeLteMax.levelInfo[i].forward != null && zslValueLteMax(lastNodeLteMax.levelInfo[i].forward.score, range)) { // 如果当前节点的后继节点仍然小于最大值,则继续前进 lastNodeLteMax = lastNodeLteMax.levelInfo[i].forward; } } /* 这里的上下文表明一定存在一个节点的值小于指定范围的最大值,因此当前节点一定不为null */ /* This is an inner range, so this node cannot be NULL. */ assert lastNodeLteMax != null; /* Check if score >= min. */ if (!zslValueGteMin(lastNodeLteMax.score, range)) { return null; } return lastNodeLteMax; } /** * 删除指定分数区间的所有节点。 * Note: 该方法引用了ZSet的哈希表视图,以便从哈希表中删除成员。 *

* Delete all the elements with score between min and max from the skiplist. * Min and max are inclusive, so a score >= min || score <= max is deleted. * Note that this function takes the reference to the hash table view of the * sorted set, in order to remove the elements from the hash table too. * * @param range 范围描述符 * @param dict 对象id到score的映射 * @return 删除的节点数量 */ int zslDeleteRangeByScore(ZScoreRangeSpec range, Map dict) { final SkipListNode[] update = updateCache; final int realLength = this.level; try { int removed = 0; SkipListNode lastNodeLtMin = this.header; for (int i = this.level - 1; i >= 0; i--) { while (lastNodeLtMin.levelInfo[i].forward != null && !zslValueGteMin(lastNodeLtMin.levelInfo[i].forward.score, range)) { lastNodeLtMin = lastNodeLtMin.levelInfo[i].forward; } update[i] = lastNodeLtMin; } /* 当前节点是小于目标范围最小值的最后一个节点,它的下一个节点可能为null,或大于等于最小值 */ /* Current node is the last with score < or <= min. */ SkipListNode firstNodeGteMin = lastNodeLtMin.levelInfo[0].forward; /* 删除在范围内的节点(小于等于最大值的节点) */ /* Delete nodes while in range. */ while (firstNodeGteMin != null && zslValueLteMax(firstNodeGteMin.score, range)) { final SkipListNode next = firstNodeGteMin.levelInfo[0].forward; zslDeleteNode(firstNodeGteMin, update); dict.remove(firstNodeGteMin.obj); removed++; firstNodeGteMin = next; } return removed; } finally { ZSetUtils.releaseUpdate(update, realLength); } } /** * 删除指定排名区间的所有成员。包括start和end。 * Note: start和end基于从1开始 *

* Delete all the elements with rank between start and end from the skiplist. * Start and end are inclusive. Note that start and end need to be 1-based * * @param start 起始排名 inclusive * @param end 截止排名 inclusive * @param dict member -> score的字典 * @return 删除的成员数量 */ int zslDeleteRangeByRank(int start, int end, Map dict) { final SkipListNode[] update = updateCache; final int realLength = this.level; try { /* 已遍历的真实成员数量,表示成员的真实排名 */ int traversed = 0; int removed = 0; SkipListNode lastNodeLtStart = this.header; for (int i = this.level - 1; i >= 0; i--) { while (lastNodeLtStart.levelInfo[i].forward != null && (traversed + lastNodeLtStart.levelInfo[i].span) < start) { // 下一个节点的排名还未到范围内,继续前进 traversed += lastNodeLtStart.levelInfo[i].span; lastNodeLtStart = lastNodeLtStart.levelInfo[i].forward; } update[i] = lastNodeLtStart; } traversed++; /* levelInfo[0] 最下面一层就是要删除节点的直接前驱 */ SkipListNode firstNodeGteStart = lastNodeLtStart.levelInfo[0].forward; while (firstNodeGteStart != null && traversed <= end) { final SkipListNode next = firstNodeGteStart.levelInfo[0].forward; zslDeleteNode(firstNodeGteStart, update); dict.remove(firstNodeGteStart.obj); removed++; traversed++; firstNodeGteStart = next; } return removed; } finally { ZSetUtils.releaseUpdate(update, realLength); } } /** * 删除指定排名的成员 - 批量删除比单个删除更快捷 * (该方法非原生方法) * * @param rank 排名 1-based * @param dict member -> score的字典 * @return 删除的节点 */ SkipListNode zslDeleteByRank(int rank, Map dict) { final SkipListNode[] update = updateCache; final int realLength = this.level; try { int traversed = 0; SkipListNode lastNodeLtStart = this.header; for (int i = this.level - 1; i >= 0; i--) { while (lastNodeLtStart.levelInfo[i].forward != null && (traversed + lastNodeLtStart.levelInfo[i].span) < rank) { // 下一个节点的排名还未到范围内,继续前进 traversed += lastNodeLtStart.levelInfo[i].span; lastNodeLtStart = lastNodeLtStart.levelInfo[i].forward; } update[i] = lastNodeLtStart; } /* levelInfo[0] 最下面一层就是要删除节点的直接前驱 */ final SkipListNode targetRankNode = lastNodeLtStart.levelInfo[0].forward; if (null != targetRankNode) { zslDeleteNode(targetRankNode, update); dict.remove(targetRankNode.obj); return targetRankNode; } else { return null; } } finally { ZSetUtils.releaseUpdate(update, realLength); } } /** * 通过score和key查找成员所属的排名。 * 如果找不到对应的成员,则返回0。 * Note:排名从1开始 *

* Find the rank for an element by both score and key. * Returns 0 when the element cannot be found, rank otherwise. * Note that the rank is 1-based due to the span of zsl->header to the * first element. * * @param score 节点分数 * @param obj 节点对应的数据id * @return 排名,从1开始 */ int zslGetRank(S score, @Nonnull K obj) { int rank = 0; SkipListNode firstNodeGteScore = this.header; for (int i = this.level - 1; i >= 0; i--) { while (firstNodeGteScore.levelInfo[i].forward != null && compareScoreAndObj(firstNodeGteScore.levelInfo[i].forward, score, obj) <= 0) { // <= 也继续前进,也就是我们期望在目标节点停下来,这样rank也不必特殊处理 rank += firstNodeGteScore.levelInfo[i].span; firstNodeGteScore = firstNodeGteScore.levelInfo[i].forward; } /* firstNodeGteScore might be equal to zsl->header, so test if firstNodeGteScore is header */ if (firstNodeGteScore != this.header && objEquals(firstNodeGteScore.obj, obj)) { // 可能在任意层找到 return rank; } } return 0; } /** * 查找指定排名的成员数据,如果不存在,则返回Null。 * 注意:排名从1开始 *

* Finds an element by its rank. The rank argument needs to be 1-based. * * @param rank 排名,1开始 * @return element */ @Nullable SkipListNode zslGetElementByRank(int rank) { int traversed = 0; SkipListNode firstNodeGteRank = this.header; for (int i = this.level - 1; i >= 0; i--) { while (firstNodeGteRank.levelInfo[i].forward != null && (traversed + firstNodeGteRank.levelInfo[i].span) <= rank) { // <= rank 表示我们期望在目标节点停下来 traversed += firstNodeGteRank.levelInfo[i].span; firstNodeGteRank = firstNodeGteRank.levelInfo[i].forward; } if (traversed == rank) { // 可能在任意层找到该排名的数据 return firstNodeGteRank; } } return null; } /** * @return 跳表中的成员数量 */ private int length() { return length; } /** * 创建一个skipList的节点 * * @param level 节点的高度 * @param score 成员分数 * @param obj 成员id * @return node */ private static SkipListNode zslCreateNode(int level, S score, K obj) { final SkipListNode node = new SkipListNode<>(obj, score, new SkipListLevel[level]); for (int index = 0; index < level; index++) { node.levelInfo[index] = new SkipListLevel<>(); } return node; } /** * 计算两个score的和 */ private S sum(S score1, S score2) { return scoreHandler.sum(score1, score2); } /** * @param start 起始分数 * @param end 截止分数 * @return spec */ private ZScoreRangeSpec newRangeSpec(S start, S end) { return newRangeSpec(start, false, end, false); } /** * @param rangeSpec 开放给用户的范围描述信息 * @return spec */ private ZScoreRangeSpec newRangeSpec(ScoreRangeSpec rangeSpec) { return newRangeSpec(rangeSpec.getStart(), rangeSpec.isStartEx(), rangeSpec.getEnd(), rangeSpec.isEndEx()); } /** * @param start 起始分数 * @param startEx 是否去除起始分数 * @param end 截止分数 * @param endEx 是否去除截止分数 * @return spec */ private ZScoreRangeSpec newRangeSpec(S start, boolean startEx, S end, boolean endEx) { if (compareScore(start, end) <= 0) { return new ZScoreRangeSpec<>(start, startEx, end, endEx); } else { return new ZScoreRangeSpec<>(end, endEx, start, startEx); } } /** * 值是否大于等于下限 * * @param value 要比较的score * @param spec 范围描述信息 * @return true/false */ @SuppressWarnings("BooleanMethodIsAlwaysInverted") boolean zslValueGteMin(S value, ZScoreRangeSpec spec) { return spec.minex ? compareScore(value, spec.min) > 0 : compareScore(value, spec.min) >= 0; } /** * 值是否小于等于上限 * * @param value 要比较的score * @param spec 范围描述信息 * @return true/false */ boolean zslValueLteMax(S value, ZScoreRangeSpec spec) { return spec.maxex ? compareScore(value, spec.max) < 0 : compareScore(value, spec.max) <= 0; } /** * 比较score和key的大小,分数作为第一排序条件,然后,相同分数的成员按照字典规则相对排序 * * @param forward 后继节点 * @param score 分数 * @param obj 成员的键 * @return 0 表示equals */ private int compareScoreAndObj(SkipListNode forward, S score, K obj) { final int scoreCompareR = compareScore(forward.score, score); if (scoreCompareR != 0) { return scoreCompareR; } return compareObj(forward.obj, obj); } /** * 比较两个成员的key,必须保证当且仅当两个键相等的时候返回0 * * @return 0表示相等 */ private int compareObj(@Nonnull K objA, @Nonnull K objB) { return objComparator.compare(objA, objB); } /** * 判断两个对象是否相等 * * @return true/false * @apiNote 使用compare == 0判断相等 */ private boolean objEquals(K objA, K objB) { // 不使用equals,而是使用compare return compareObj(objA, objB) == 0; } /** * 比较两个分数的大小 * * @return 0表示相等 */ private int compareScore(S score1, S score2) { return scoreHandler.compare(score1, score2); } /** * 判断第一个分数是否和第二个分数相等 * * @return true/false * @apiNote 使用compare == 0判断相等 */ private boolean scoreEquals(S score1, S score2) { return compareScore(score1, score2) == 0; } /** * 获取跳表的堆内存视图 * * @return string */ String dump() { final StringBuilder sb = new StringBuilder("{level = 0, nodeArray:[\n"); SkipListNode curNode = this.header.directForward(); int rank = 0; while (curNode != null) { sb.append("{rank:").append(rank++) .append(",obj:").append(curNode.obj) .append(",score:").append(curNode.score); curNode = curNode.directForward(); if (curNode != null) { sb.append("},\n"); } else { sb.append("}\n"); } } return sb.append("]}").toString(); } } /** * 跳表节点 */ private static class SkipListNode implements Entry { /** * 节点对应的数据id */ final K obj; /** * 该节点数据对应的评分 - 如果要通用的话,这里将来将是一个泛型对象,需要实现{@link Comparable}。 */ final S score; /** * 该节点的层级信息 * level[]存放指向各层链表后一个节点的指针(后向指针)。 */ final SkipListLevel[] levelInfo; /** * 该节点的前向指针 * NOTE:(不包含header) * backward字段是指向链表前一个节点的指针(前向指针)。 * 节点只有1个前向指针,所以只有第1层链表是一个双向链表。 */ SkipListNode backward; private SkipListNode(K obj, S score, SkipListLevel[] levelInfo) { this.obj = obj; this.score = score; // noinspection unchecked this.levelInfo = levelInfo; } /** * @return 该节点的直接后继节点 */ SkipListNode directForward() { return levelInfo[0].forward; } @Override public K getMember() { return obj; } @Override public S getScore() { return score; } } /** * 跳表层级 */ private static class SkipListLevel { /** * 每层对应1个后向指针 (后继节点) */ SkipListNode forward; /** * 到后继节点之间的跨度 * 它表示当前的指针跨越了多少个节点。span用于计算成员排名(rank),这是Redis对于SkipList做的一个扩展。 */ int span; } // region 迭代 /** * ZSet迭代器 * Q: 为什么不写在{@link SkipList}中? * A: 因为删除数据需要访问{@link #dict}。 */ private class ZSetItr implements Iterator> { private SkipListNode lastReturned; private SkipListNode next; int expectedModCount = zsl.modCount; ZSetItr(SkipListNode next) { this.next = next; } @Override public boolean hasNext() { return next != null; } @Override public Entry next() { checkForComodification(); if (next == null) { throw new NoSuchElementException(); } lastReturned = next; next = next.directForward(); return nextMember(lastReturned); } protected Entry nextMember(SkipListNode lastReturned) { return new ZSetEntry<>(lastReturned.obj, lastReturned.score); } @Override public void remove() { if (lastReturned == null) { throw new IllegalStateException(); } checkForComodification(); // remove lastReturned dict.remove(lastReturned.obj); zsl.zslDelete(lastReturned.score, lastReturned.obj); // reset lastReturned lastReturned = null; expectedModCount = zsl.modCount; } final void checkForComodification() { if (zsl.modCount != expectedModCount) { throw new ConcurrentModificationException(); } } } private class FastZSetItr extends ZSetItr { FastZSetItr(SkipListNode next) { super(next); } @Override protected Entry nextMember(SkipListNode lastReturned) { return lastReturned; } } private static class ZSetEntry implements Entry { private final K member; private final S score; ZSetEntry(K member, S score) { this.member = member; this.score = score; } @Override public K getMember() { return member; } @Override public S getScore() { return score; } @Override public String toString() { return "{" + "member=" + member + ", score=" + score + '}'; } } // endregion } ================================================ FILE: gamioo-redis/src/main/java/io/gamioo/redis/zset/generic/ScoreHandler.java ================================================ /* * Copyright 2019 wjybxx * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to iBn writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.redis.zset.generic; import java.util.Comparator; /** * 泛型化的分数处理器 * * Note: * 1. score对象必须实现为不可变,一定不可以修改里面的内容。 * 2. {@link #sum(Object, Object)}必须返回一个新对象。 * 3. 不要在score对象中存储杂七杂八的属性,如果想存储一些额外的数据,请存储在key中。 * * @param the type of score * @author wjybxx * @version 1.0 * date - 2019/11/6 */ public interface ScoreHandler extends Comparator { /** * 比较两个score的大小。 * * @param o1 score * @param o2 score * @return 0表示相等 */ @Override int compare(T o1, T o2); /** * 计算两个score的和 * * @param oldScore 当前分数 * @param increment 增量 * @return newInstance */ T sum(T oldScore, T increment); } ================================================ FILE: gamioo-redis/src/main/java/io/gamioo/redis/zset/generic/ScoreHandlers.java ================================================ /* * Copyright 2019 wjybxx * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to iBn writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.redis.zset.generic; /** * 存放一些常用的{@link ScoreHandler}实现。 * * @author wjybxx * @version 1.0 * date - 2019/11/6 */ public class ScoreHandlers { private ScoreHandlers() { } /** * @return Long类型的score处理器 */ public static ScoreHandler longScoreHandler() { return longScoreHandler(false); } /** * @param desc 是否降序 * @return Long类型的score处理器 */ public static ScoreHandler longScoreHandler(boolean desc) { return desc ? DescLongScoreHandler.INSTANCE : LongScoreHandler.INSTANCE; } /** * Long类型的score处理器 - 升序 */ private static class LongScoreHandler implements ScoreHandler { private static final LongScoreHandler INSTANCE = new LongScoreHandler(); private LongScoreHandler() { } @Override public int compare(Long o1, Long o2) { return o1.compareTo(o2); } @Override public Long sum(Long oldScore, Long increment) { return oldScore + increment; } } /** * Long类型的score处理器 - 降序 */ private static class DescLongScoreHandler implements ScoreHandler { private static final DescLongScoreHandler INSTANCE = new DescLongScoreHandler(); private DescLongScoreHandler() { } @Override public int compare(Long o1, Long o2) { // 逆序 return o2.compareTo(o1); } @Override public Long sum(Long oldScore, Long increment) { return oldScore + increment; } } } ================================================ FILE: gamioo-redis/src/main/java/io/gamioo/redis/zset/generic/ScoreRangeSpec.java ================================================ /* * Copyright 2019 wjybxx * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to iBn writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.redis.zset.generic; /** * Score范围区间 * * @author wjybxx * @version 1.0 * date - 2019/11/7 */ public class ScoreRangeSpec { /** * 起始分数 - 最高分或最低分 */ private final S start; /** * 是否去除起始分数 * exclusive */ private final boolean startEx; /** * 截止分数 - 最高分或最低分 */ private final S end; /** * 是否去除最高分 * exclusive */ private final boolean endEx; public ScoreRangeSpec(S start, S end) { this(start, false, end, false); } public ScoreRangeSpec(S start, boolean startEx, S end, boolean endEx) { this.start = start; this.startEx = startEx; this.end = end; this.endEx = endEx; } public S getStart() { return start; } public boolean isStartEx() { return startEx; } public S getEnd() { return end; } public boolean isEndEx() { return endEx; } } ================================================ FILE: gamioo-redis/src/main/java/io/gamioo/redis/zset/generic/ZScoreRangeSpec.java ================================================ package io.gamioo.redis.zset.generic; /** * {@link GenericZSet}中“score”范围描述信息 - specification模式 */ public class ZScoreRangeSpec { /** * 最低分数 */ public final S min; /** * 是否去除最低分 * exclusive */ public final boolean minex; /** * 最高分数 */ public final S max; /** * 是否去除最高分 * exclusive */ public final boolean maxex; public ZScoreRangeSpec(S min, boolean minex, S max, boolean maxex) { this.min = min; this.minex = minex; this.max = max; this.maxex = maxex; } } ================================================ FILE: gamioo-redis/src/main/java/io/gamioo/redis/zset/long2object/Long2ObjectEntry.java ================================================ /* * Copyright 2019 wjybxx * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to iBn writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.redis.zset.long2object; import io.gamioo.redis.zset.generic.Entry; /** * zset中单个成员信息 */ public interface Long2ObjectEntry extends Entry { @Deprecated @Override default Long getMember() { return getLongMember(); } long getLongMember(); @Override S getScore(); } ================================================ FILE: gamioo-redis/src/main/java/io/gamioo/redis/zset/long2object/Long2ObjectZSet.java ================================================ /* * Copyright 2019 wjybxx * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to iBn writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.redis.zset.long2object; import io.gamioo.redis.zset.ZSetUtils; import io.gamioo.redis.zset.generic.Entry; import io.gamioo.redis.zset.generic.ScoreHandler; import io.gamioo.redis.zset.generic.ScoreRangeSpec; import io.gamioo.redis.zset.generic.ZScoreRangeSpec; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.LongComparator; import it.unimi.dsi.fastutil.longs.LongComparators; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.concurrent.NotThreadSafe; import java.util.*; import static io.gamioo.redis.zset.ZSetUtils.ZSKIPLIST_MAXLEVEL; /** * key为long类型,score为泛型类型的sorted set - 参考redis的zset实现 * 排序规则 * 有序集合里面的成员是不能重复的,都是唯一的,但是,不同成员间有可能有相同的分数。 * 当多个成员有相同的分数时,它们将按照键排序。 * 即:分数作为第一排序条件,键作为第二排序条件,当分数相同时,比较键的大小。 *

* NOTE: * 1. ZSET中的排名从0开始(提供给用户的接口,排名都从0开始) *

* 2. 我们允许zset中的成员是降序排列的-{@link ScoreHandler}决定,可以更好的支持根据score降序的排行榜, * 而不是强迫你总是调用反转系列接口{@code zrev...},那样的设计不符合人的正常思维,就很容易出错。 *

* 3. 我们修改了redis中根据min和max查找和删除成员的接口,修改为start和end,当根据score范围查找或删除元素时,并不要求start小于等于end,我们会处理它们的大小关系。
* Q: 为什么要这么改动呢?
* A: 举个栗子:假如ScoreHandler比较两个long类型的score是逆序的,现在要删除排行榜中 1-10000分的成员,如果方法告诉你要传入的的是min和max, * 你会很自然的传入想到 (1,10000) 而不是 (10000,1)。因此,如果接口不做调整,这个接口就太反人类了,谁用都得错。 *

* 4. score泛型化以后,有一些注意事项,请查看{@link ScoreHandler}中关于score的注意事项。 * *

* 这里只实现了redis zset中的常用的接口,扩展不是太麻烦,可以自己根据需要实现。 * * @param the type of score * @author wjybxx * @version 1.0 * date - 2019/11/4 */ @NotThreadSafe public class Long2ObjectZSet implements Iterable> { /** * member -> score */ private final Long2ObjectMap dict = new Long2ObjectOpenHashMap<>(ZSetUtils.INIT_CAPACITY); private final SkipList zsl; private Long2ObjectZSet(LongComparator objComparator, ScoreHandler scoreHandler) { this.zsl = new SkipList<>(objComparator, scoreHandler); } /** * 创建一个键为long类型的zset * * @param scoreHandler 分数处理器 * @param score类型 * @return zset */ public static Long2ObjectZSet newZSet(ScoreHandler scoreHandler) { return new Long2ObjectZSet<>(LongComparators.NATURAL_COMPARATOR, scoreHandler); } /** * 创建一个自定义键类型的zset * * @param objComparator 键比较器,当score比较结果相等时,比较key。 * @param scoreHandler 分数处理器 * @param score的类型 * @return zset */ public static Long2ObjectZSet newZSet(LongComparator objComparator, ScoreHandler scoreHandler) { return new Long2ObjectZSet<>(objComparator, scoreHandler); } // -------------------------------------------------------- insert ----------------------------------------------- /** * 往有序集合中新增一个成员。 * 如果指定添加的成员已经是有序集合里面的成员,则会更新成员的分数(score)并更新到正确的排序位置。 * * @param score 数据的评分 * @param member 成员id */ public void zadd(final S score, final long member) { Objects.requireNonNull(score); final S oldScore = dict.put(member, score); if (oldScore != null) { // Q: 为何不再判断分数相等? // A: 这里假定分数相等的情况很少出现,可减少大量无用的判断 zsl.zslDelete(oldScore, member); } zsl.zslInsert(score, member); } /** * 往有序集合中新增一个成员。当且仅当该成员不在有序集合时才添加。 * * @param score 数据的评分 * @param member 成员id * @return 添加成功则返回true,否则返回false。 */ public boolean zaddnx(final S score, final long member) { Objects.requireNonNull(score); final S oldScore = dict.putIfAbsent(member, score); if (oldScore == null) { zsl.zslInsert(score, member); return true; } return false; } /** * 为有序集的成员member的score值加上增量increment,并更新到正确的排序位置。 * 如果有序集中不存在member,就在有序集中添加一个member,score是increment(就好像它之前的score是0) * * @param increment 自定义增量 * @param member 成员id * @return 更新后的值 */ public S zincrby(S increment, long member) { Objects.requireNonNull(increment); final S oldScore = dict.get(member); final S score = oldScore == null ? increment : zsl.sum(oldScore, increment); zadd(score, member); return score; } /** * 为有序集的成员member的score值加上增量increment,并更新到正确的排序位置。 * 如果有序集中不存在member,则放弃更新并返回null。 * * @param increment 自定义增量 * @param member 成员id * @return 更新后的值,如果更新失败,则返回null。 */ public S zincrbyxx(S increment, long member) { Objects.requireNonNull(increment); final S oldScore = dict.get(member); if (oldScore == null) { return null; } final S score = zsl.sum(oldScore, increment); zadd(score, member); return score; } // -------------------------------------------------------- remove ----------------------------------------------- /** * 删除指定成员 * * @param member 成员id * @return 如果成员存在,则返回对应的score,否则返回null。 */ public S zrem(long member) { final S oldScore = dict.remove(member); if (oldScore != null) { zsl.zslDelete(oldScore, member); return oldScore; } else { return null; } } // region 通过score删除成员 /** * 移除zset中所有score值介于start和end之间(包括等于start或end)的成员 * * @param start 起始分数 inclusive * @param end 截止分数 inclusive * @return 删除的成员数目 */ public int zremrangeByScore(S start, S end) { return zremrangeByScore(zsl.newRangeSpec(start, end)); } /** * 移除zset中所有score值在范围区间的成员 * * @param spec score范围区间 * @return 删除的成员数目 */ private int zremrangeByScore(@Nonnull ScoreRangeSpec spec) { return zremrangeByScore(zsl.newRangeSpec(spec)); } /** * 移除zset中所有score值在范围区间的成员 * * @param spec score范围区间 * @return 删除的成员数目 */ private int zremrangeByScore(@Nonnull ZScoreRangeSpec spec) { return zsl.zslDeleteRangeByScore(spec, dict); } // endregion // region 通过排名删除成员 /** * 删除并返回有序集合中的第一个成员。 * - 不使用min和max,是因为score的比较方式是用户自定义的。 * * @return 如果不存在,则返回null */ @Nullable public Long2ObjectEntry zpopFirst() { return zremByRank(0); } /** * 删除并返回有序集合中的最后一个成员。 * - 不使用min和max,是因为score的比较方式是用户自定义的。 * * @return 如果不存在,则返回null */ @Nullable public Long2ObjectEntry zpopLast() { return zremByRank(zsl.length() - 1); } /** * 删除指定排名的成员 * * @param rank 排名 0-based * @return 删除成功则返回该排名对应的数据,否则返回null */ @Nullable public Long2ObjectEntry zremByRank(int rank) { if (rank < 0 || rank >= zsl.length()) { return null; } final SkipListNode delete = zsl.zslDeleteByRank(rank + 1, dict); assert null != delete; return new ZSetEntry<>(delete.obj, delete.score); } /** * 删除指定排名范围的全部成员,start和end都是从0开始的。 * 排名0表示分数最小的成员。 * start和end都可以是负数,此时它们表示从最高排名成员开始的偏移量,eg: -1表示最高排名的成员, -2表示第二高分的成员,以此类推。 *

* Time complexity: O(log(N))+O(M) with N being the number of elements in the sorted set * and M the number of elements removed by the operation * * @param start 起始排名 * @param end 截止排名 * @return 删除的成员数目 */ public int zremrangeByRank(int start, int end) { final int zslLength = zsl.length(); start = ZSetUtils.convertStartRank(start, zslLength); end = ZSetUtils.convertEndRank(end, zslLength); if (ZSetUtils.isRankRangeEmpty(start, end, zslLength)) { return 0; } return zsl.zslDeleteRangeByRank(start + 1, end + 1, dict); } // endregion // region 限制成员数量 /** * 删除zset中尾部多余的成员,将zset中的成员数量限制到count之内。 * 保留前面的count个数成员 * * @param count 剩余数量限制 * @return 删除的成员数量 */ public int zlimit(int count) { if (zsl.length() <= count) { return 0; } return zsl.zslDeleteRangeByRank(count + 1, zsl.length(), dict); } /** * 删除zset中头部多余的成员,将zset中的成员数量限制到count之内。 * - 保留后面的count个数成员 * * @param count 剩余数量限制 * @return 删除的成员数量 */ public int zrevlimit(int count) { if (zsl.length() <= count) { return 0; } return zsl.zslDeleteRangeByRank(1, zsl.length() - count, dict); } // endregion // -------------------------------------------------------- query ----------------------------------------------- /** * 返回有序集成员member的score值。 * 如果member成员不是有序集的成员,返回null - 这里返回任意的基础值都是不合理的,因此必须返回null。 * * @param member 成员id * @return score */ public S zscore(long member) { return dict.get(member); } public boolean contain(long member) { return dict.containsKey(member); } /** * 返回有序集中成员member的排名。 *

* Time complexity: O(log(N)) *

* 与redis的区别:我们使用-1表示成员不存在,而不是返回null。 * * @param member 成员id * @return 如果存在该成员,则返回该成员的排名(0-based),否则返回-1 */ public int zrank(long member) { final S score = dict.get(member); if (score == null) { return -1; } // 0 < zslGetRank <= size return zsl.zslGetRank(score, member) - 1; } /** * 返回有序集中成员member的逆序排名。 *

* Time complexity: O(log(N)) *

* 与redis的区别:我们使用-1表示成员不存在,而不是返回null。 * * @param member 成员id * @return 如果存在该成员,则返回该成员的排名(0-based),否则返回-1 */ public int zrevrank(long member) { final S score = dict.get(member); if (score == null) { return -1; } // 0 < zslGetRank <= size return zsl.length() - zsl.zslGetRank(score, member); } /** * 获取指定排名的成员数据。 * * @param rank 排名 0-based * @return memver,如果不存在,则返回null */ public Long2ObjectEntry zmemberByRank(int rank) { if (rank < 0 || rank >= zsl.length()) { return null; } final SkipListNode node = zsl.zslGetElementByRank(rank + 1); assert null != node; return new ZSetEntry<>(node.obj, node.score); } /** * 获取指定逆序排名的成员数据。 * * @param rank 排名 0-based * @return memver,如果不存在,则返回null */ public Long2ObjectEntry zrevmemberByRank(int rank) { if (rank < 0 || rank >= zsl.length()) { return null; } final SkipListNode node = zsl.zslGetElementByRank(zsl.length() - rank); assert null != node; return new ZSetEntry<>(node.obj, node.score); } // region 通过分数查询 /** * 返回有序集合中的分数在start和end之间的所有成员(包括分数等于start或者end的成员)。 * * @param start 起始分数 inclusive * @param end 截止分数 inclusive * @return memberInfo */ public List> zrangeByScore(S start, S end) { return zrangeByScoreWithOptions(zsl.newRangeSpec(start, end), 0, -1, false); } /** * 返回有序集合中的分数在指定范围区间的所有成员。 * * @param spec 范围描述信息 * @return memberInfo */ public List> zrangeByScore(ScoreRangeSpec spec) { return zrangeByScoreWithOptions(zsl.newRangeSpec(spec), 0, -1, false); } /** * 返回有序集合中的分数在start和end之间的所有成员(包括分数等于start或者end的成员),返回的成员按照逆序排列。 * * @param start 起始分数 inclusive * @param end 截止分数 inclusive * @return memberInfo */ public List> zrevrangeByScore(final S start, final S end) { return zrangeByScoreWithOptions(zsl.newRangeSpec(start, end), 0, -1, true); } /** * 返回有序集合中的分数在指定范围之间的所有成员,返回的成员按照逆序排列。 * * @param rangeSpec score范围区间 * @return 删除的成员数目 */ public List> zrevrangeByScore(ScoreRangeSpec rangeSpec) { return zrangeByScoreWithOptions(zsl.newRangeSpec(rangeSpec), 0, -1, true); } /** * 返回zset中指定分数区间内的成员,并按照指定顺序返回 * * @param rangeSpec score范围描述信息 * @param offset 偏移量(用于分页) 大于等于0 * @param limit 返回的成员数量(用于分页) 小于0表示不限制 * @param reverse 是否逆序 * @return memberInfo */ public List> zrangeByScoreWithOptions(final ScoreRangeSpec rangeSpec, int offset, int limit, boolean reverse) { return zrangeByScoreWithOptions(zsl.newRangeSpec(rangeSpec), offset, limit, reverse); } /** * 返回zset中指定分数区间内的成员,并按照指定顺序返回 * * @param range score范围描述信息 * @param offset 偏移量(用于分页) 大于等于0 * @param limit 返回的成员数量(用于分页) 小于0表示不限制 * @param reverse 是否逆序 * @return memberInfo */ private List> zrangeByScoreWithOptions(final ZScoreRangeSpec range, int offset, int limit, boolean reverse) { if (offset < 0) { throw new IllegalArgumentException("offset" + ": " + offset + " (expected: >= 0)"); } SkipListNode listNode; /* If reversed, get the last node in range as starting point. */ if (reverse) { listNode = zsl.zslLastInRange(range); } else { listNode = zsl.zslFirstInRange(range); } /* No "first" element in the specified interval. */ if (listNode == null) { return new ArrayList<>(); } /* If there is an offset, just traverse the number of elements without * checking the score because that is done in the next loop. */ while (listNode != null && offset-- != 0) { if (reverse) { listNode = listNode.backward; } else { listNode = listNode.levelInfo[0].forward; } } final List> result = new ArrayList<>(); /* 这里使用 != 0 判断,当limit小于0时,表示不限制 */ while (listNode != null && limit-- != 0) { /* Abort when the node is no longer in range. */ if (reverse) { if (!zsl.zslValueGteMin(listNode.score, range)) { break; } } else { if (!zsl.zslValueLteMax(listNode.score, range)) { break; } } result.add(new ZSetEntry<>(listNode.obj, listNode.score)); /* Move to next node */ if (reverse) { listNode = listNode.backward; } else { listNode = listNode.levelInfo[0].forward; } } return result; } // endregion // region 通过排名查询 /** * 查询指定排名区间的成员信息 * * @param start 起始排名(0-based) inclusive * @param end 截止排名(0-based) inclusive * @return memberInfo */ public List> zrangeByRank(int start, int end) { return zrangeByRankInternal(start, end, false); } /** * 查询指定逆序排名区间的成员信息 * * @param start 起始排名(0-based) inclusive * @param end 截止排名(0-based) inclusive * @return memberInfo */ public List> zrevrangeByRank(int start, int end) { return zrangeByRankInternal(start, end, true); } /** * 查询指定排名区间的成员id和分数,start和end都是从0开始的。 * * @param start 起始排名(0-based) inclusive * @param end 截止排名(0-based) inclusive * @param reverse 是否逆序返回 * @return memberInfo */ private List> zrangeByRankInternal(int start, int end, boolean reverse) { final int zslLength = zsl.length(); start = ZSetUtils.convertStartRank(start, zslLength); end = ZSetUtils.convertEndRank(end, zslLength); if (ZSetUtils.isRankRangeEmpty(start, end, zslLength)) { return new ArrayList<>(); } int rangeLen = end - start + 1; SkipListNode listNode; /* start >= 0,大于0表示需要进行调整 */ /* Check if starting point is trivial, before doing log(N) lookup. */ if (reverse) { listNode = start > 0 ? zsl.zslGetElementByRank(zslLength - start) : zsl.tail; } else { listNode = start > 0 ? zsl.zslGetElementByRank(start + 1) : zsl.header.levelInfo[0].forward; } final List> result = new ArrayList<>(rangeLen); while (rangeLen-- > 0 && listNode != null) { result.add(new ZSetEntry<>(listNode.obj, listNode.score)); listNode = reverse ? listNode.backward : listNode.levelInfo[0].forward; } return result; } // endregion // region 统计分数人数 /** * 返回有序集key中,score值在指定区间(包括score值等于start或end)的成员 * * @param start 起始分数 * @param end 截止分数 * @return 分数区间段内的成员数量 */ public int zcount(S start, S end) { return zcountInternal(zsl.newRangeSpec(start, end)); } /** * 返回有序集key中,score值在指定区间的成员 * * @param rangeSpec score区间描述信息 * @return 分数区间段内的成员数量 */ public int zcount(ScoreRangeSpec rangeSpec) { return zcountInternal(zsl.newRangeSpec(rangeSpec)); } /** * 返回有序集key中,score值在指定区间的成员 * * @param range score区间描述信息 * @return 分数区间段内的成员数量 */ private int zcountInternal(final ZScoreRangeSpec range) { final SkipListNode firstNodeInRange = zsl.zslFirstInRange(range); if (firstNodeInRange != null) { final int firstNodeRank = zsl.zslGetRank(firstNodeInRange.score, firstNodeInRange.obj); /* 如果firstNodeInRange不为null,那么lastNode也一定不为null(最坏的情况下firstNode就是lastNode) */ final SkipListNode lastNodeInRange = zsl.zslLastInRange(range); assert lastNodeInRange != null; final int lastNodeRank = zsl.zslGetRank(lastNodeInRange.score, lastNodeInRange.obj); return lastNodeRank - firstNodeRank + 1; } return 0; } /** * @return zset中的成员数量 */ public int zcard() { return zsl.length(); } // endregion // region 迭代 /** * 迭代有序集中的所有元素 * * @return iterator */ @Nonnull public Iterator> zscan() { return zscan(0); } /** * 从指定偏移量开始迭代有序集中的元素 * * @param offset 偏移量,如果小于等于0,则等价于{@link #zscan()} * @return iterator */ @Nonnull public Iterator> zscan(int offset) { if (offset <= 0) { return new ZSetItr(zsl.header.directForward()); } if (offset >= zsl.length()) { return new ZSetItr(null); } return new ZSetItr(zsl.zslGetElementByRank(offset + 1)); } @Nonnull @Override public Iterator> iterator() { return zscan(0); } /** * {@link Iterator#next()}总是返回相同的{@link Entry}对象,外部在迭代时不可以保持其引用。 * 改迭代器是为了避免创建大量的{@link Entry}对象。 * * @param offset 偏移量 * @return 数组 */ @Nonnull public Iterator> fastzscan(int offset) { if (offset <= 0) { return new FastZSetItr(zsl.header.directForward()); } if (offset >= zsl.length()) { return new FastZSetItr(null); } return new FastZSetItr(zsl.zslGetElementByRank(offset + 1)); } /** * @return 总是返回相同的{@link Entry}对象,外部在迭代时不可以保持其引用。 * 改迭代器是为了避免创建大量的{@link Entry}对象。 */ @Nonnull public Iterator> fastIterator() { return fastzscan(0); } // endregion /** * @return zset中当前的成员信息,用于debug */ public String dump() { return zsl.dump(); } // ------------------------------------------------------- 内部实现 ---------------------------------------- /** * 跳表 * 注意:跳表的排名是从1开始的。 * * @author wjybxx * @version 1.0 * date - 2019/11/4 */ private static class SkipList { /** * 更新节点使用的缓存 - 避免频繁的申请空间 */ @SuppressWarnings("unchecked") private final SkipListNode[] updateCache = new SkipListNode[ZSKIPLIST_MAXLEVEL]; private final int[] rankCache = new int[ZSKIPLIST_MAXLEVEL]; private final LongComparator objComparator; private final ScoreHandler scoreHandler; /** * 修改次数 - 防止错误的迭代 */ private int modCount = 0; /** * 跳表头结点 - 哨兵 * 1. 可以简化判定逻辑 * 2. 恰好可以使得rank从1开始 */ private final SkipListNode header; /** * 跳表尾节点 */ private SkipListNode tail; /** * 跳表成员个数 * 注意:head头指针不包含在length计数中。 */ private int length = 0; /** * level表示SkipList的总层数,即所有节点层数的最大值。 */ private int level = 1; SkipList(LongComparator objComparator, ScoreHandler scoreHandler) { this.objComparator = objComparator; this.scoreHandler = scoreHandler; this.header = zslCreateNode(ZSKIPLIST_MAXLEVEL, null, 0); } /** * 插入一个新的节点到跳表。 * 这里假定成员已经不存在(直到调用方执行该方法)。 *

* zslInsert a new node in the skiplist. Assumes the element does not already * exist (up to the caller to enforce that). *

         *             header                    newNode
         *               _                                                 _
         * level - 1    |_| pre                                           |_|
         *  |           |_| pre                    _                      |_|
         *  |           |_| pre  _                |_|                     |_|
         *  |           |_|  ↓  |_| pre  _        |_|      _              |_|
         *  |           |_|     |_|  ↓  |_| pre   |_|     |_|             |_|
         *  |           |_|     |_|     |_| pre   |_|     |_|      _      |_|
         *  |           |_|     |_|     |_| pre   |_|     |_|     |_|     |_|
         *  0           |0|     |1|     |2| pre   |_|     |3|     |4|     |5|
         * 
* * @param score 分数 * @param obj obj 分数对应的成员id */ @SuppressWarnings("UnusedReturnValue") SkipListNode zslInsert(S score, long obj) { // 新节点的level final int level = ZSetUtils.zslRandomLevel(); // update - 需要更新后继节点的Node,新节点各层的前驱节点 // 1. 分数小的节点 // 2. 分数相同但id小的节点(分数相同时根据数据排序) // rank - 新节点各层前驱的当前排名 // 这里不必创建一个ZSKIPLIST_MAXLEVEL长度的数组,它取决于插入节点后的新高度,你在别处看见的代码会造成大量的空间浪费,增加GC压力。 // 如果创建的都是ZSKIPLIST_MAXLEVEL长度的数组,那么应该实现缓存 final SkipListNode[] update = updateCache; final int[] rank = rankCache; final int realLength = Math.max(level, this.level); try { // preNode - 新插入节点的前驱节点 SkipListNode preNode = header; for (int i = this.level - 1; i >= 0; i--) { /* store rank that is crossed to reach the insert position */ if (i == (this.level - 1)) { // 起始点,也就是head,它的排名就是0 rank[i] = 0; } else { // 由于是回溯降级继续遍历,因此其初始排名是前一次遍历的排名 rank[i] = rank[i + 1]; } while (preNode.levelInfo[i].forward != null && compareScoreAndObj(preNode.levelInfo[i].forward, score, obj) < 0) { // preNode的后继节点仍然小于要插入的节点,需要继续前进,同时累计排名 rank[i] += preNode.levelInfo[i].span; preNode = preNode.levelInfo[i].forward; } // 这是要插入节点的第i层的前驱节点,此时触发降级 update[i] = preNode; } if (level > this.level) { /* 新节点的层级大于当前层级,那么高出来的层级导致需要更新head,且排名和跨度是固定的 */ for (int i = this.level; i < level; i++) { rank[i] = 0; update[i] = this.header; update[i].levelInfo[i].span = this.length; } this.level = level; } /* 由于我们允许的重复score,并且zslInsert(该方法)的调用者在插入前必须测试要插入的member是否已经在hash表中。 * 因此我们假设key(obj)尚未被插入,并且重复插入score的情况永远不会发生。*/ /* we assume the key is not already inside, since we allow duplicated * scores, and the re-insertion of score and redis object should never * happen since the caller of zslInsert() should test in the hash table * if the element is already inside or not.*/ final SkipListNode newNode = zslCreateNode(level, score, obj); /* 这些节点的高度小于等于新插入的节点的高度,需要更新指针。此外它们当前的跨度被拆分了两部分,需要重新计算。 */ for (int i = 0; i < level; i++) { /* 链接新插入的节点 */ newNode.levelInfo[i].forward = update[i].levelInfo[i].forward; update[i].levelInfo[i].forward = newNode; /* rank[0] 是新节点的直接前驱的排名,每一层都有一个前驱,可以通过彼此的排名计算跨度 */ /* 计算新插入节点的跨度 和 重新计算所有前驱节点的跨度,之前的跨度被拆分为了两份*/ /* update span covered by update[i] as newNode is inserted here */ newNode.levelInfo[i].span = update[i].levelInfo[i].span - (rank[0] - rank[i]); update[i].levelInfo[i].span = (rank[0] - rank[i]) + 1; } /* 这些节点高于新插入的节点,它们的跨度可以简单的+1 */ /* increment span for untouched levels */ for (int i = level; i < this.level; i++) { update[i].levelInfo[i].span++; } /* 设置新节点的前向节点(回溯节点) - 这里不包含header,一定注意 */ newNode.backward = (update[0] == this.header) ? null : update[0]; /* 设置新节点的后向节点 */ if (newNode.levelInfo[0].forward != null) { newNode.levelInfo[0].forward.backward = newNode; } else { this.tail = newNode; } this.length++; this.modCount++; return newNode; } finally { ZSetUtils.releaseUpdate(update, realLength); ZSetUtils.releaseRank(rank, realLength); } } /** * Delete an element with matching score/object from the skiplist. * * @param score 分数用于快速定位节点 * @param obj 用于确定节点是否是对应的数据节点 */ @SuppressWarnings("UnusedReturnValue") boolean zslDelete(S score, long obj) { // update - 需要更新后继节点的Node // 1. 分数小的节点 // 2. 分数相同但id小的节点(分数相同时根据数据排序) final SkipListNode[] update = updateCache; final int realLength = this.level; try { SkipListNode preNode = this.header; for (int i = this.level - 1; i >= 0; i--) { while (preNode.levelInfo[i].forward != null && compareScoreAndObj(preNode.levelInfo[i].forward, score, obj) < 0) { // preNode的后继节点仍然小于要删除的节点,需要继续前进 preNode = preNode.levelInfo[i].forward; } // 这是目标节点第i层的可能前驱节点 update[i] = preNode; } /* 由于可能多个节点拥有相同的分数,因此必须同时比较score和object */ /* We may have multiple elements with the same score, what we need * is to find the element with both the right score and object. */ final SkipListNode targetNode = preNode.levelInfo[0].forward; if (targetNode != null && scoreEquals(targetNode.score, score) && objEquals(targetNode.obj, obj)) { zslDeleteNode(targetNode, update); return true; } /* not found */ return false; } finally { ZSetUtils.releaseUpdate(update, realLength); } } /** * Internal function used by zslDelete, zslDeleteByScore and zslDeleteByRank * * @param deleteNode 要删除的节点 * @param update 可能要更新的节点们 */ private void zslDeleteNode(final SkipListNode deleteNode, final SkipListNode[] update) { for (int i = 0; i < this.level; i++) { if (update[i].levelInfo[i].forward == deleteNode) { // 这些节点的高度小于等于要删除的节点,需要合并两个跨度 update[i].levelInfo[i].span += deleteNode.levelInfo[i].span - 1; update[i].levelInfo[i].forward = deleteNode.levelInfo[i].forward; } else { // 这些节点的高度高于要删除的节点,它们的跨度可以简单的 -1 update[i].levelInfo[i].span--; } } if (deleteNode.levelInfo[0].forward != null) { // 要删除的节点有后继节点 deleteNode.levelInfo[0].forward.backward = deleteNode.backward; } else { // 要删除的节点是tail节点 this.tail = deleteNode.backward; } // 如果删除的节点是最高等级的节点,则检查是否需要降级 if (deleteNode.levelInfo.length == this.level) { while (this.level > 1 && this.header.levelInfo[this.level - 1].forward == null) { // 如果最高层没有后继节点,则降级 this.level--; } } this.length--; this.modCount++; } /** * 判断zset中的数据所属的范围是否和指定range存在交集(intersection)。 * 它不代表zset存在指定范围内的数据。 * Returns if there is a part of the zset is in range. *
         *                         ZSet
         *              min ____________________ max
         *                 |____________________|
         *   min ______________ max  min _____________
         *      |______________|        |_____________|
         *          Range                   Range
         * 
* * @param range 范围描述信息 * @return true/false */ @SuppressWarnings("BooleanMethodIsAlwaysInverted") boolean zslIsInRange(ZScoreRangeSpec range) { if (isScoreRangeEmpty(range)) { // 传进来的范围为空 return false; } if (this.tail == null || !zslValueGteMin(this.tail.score, range)) { // 列表有序,按照从score小到大,如果尾部节点数据小于最小值,那么一定不在区间范围内 return false; } final SkipListNode firstNode = this.header.levelInfo[0].forward; if (firstNode == null || !zslValueLteMax(firstNode.score, range)) { // 列表有序,按照从score小到大,如果首部节点数据大于最大值,那么一定不在范围内 return false; } return true; } /** * 测试score范围信息是否为空(无效) * * @param range 范围描述信息 * @return true/false */ private boolean isScoreRangeEmpty(ZScoreRangeSpec range) { // 这里和redis有所区别,这里min一定小于等于max return scoreEquals(range.min, range.max) && (range.minex || range.maxex); } /** * 找出第一个在指定范围内的节点。如果没有符合的节点,则返回null。 *

* Find the first node that is contained in the specified range. * Returns NULL when no element is contained in the range. * * @param range 范围描述符 * @return 不存在返回null */ @Nullable SkipListNode zslFirstInRange(ZScoreRangeSpec range) { /* zset数据范围与指定范围没有交集,可提前返回,减少不必要的遍历 */ /* If everything is out of range, return early. */ if (!zslIsInRange(range)) { return null; } SkipListNode lastNodeLtMin = this.header; for (int i = this.level - 1; i >= 0; i--) { /* 前进直到出现后继节点大于等于指定最小值的节点 */ /* Go forward while *OUT* of range. */ while (lastNodeLtMin.levelInfo[i].forward != null && !zslValueGteMin(lastNodeLtMin.levelInfo[i].forward.score, range)) { // 如果当前节点的后继节点仍然小于指定范围的最小值,则继续前进 lastNodeLtMin = lastNodeLtMin.levelInfo[i].forward; } } /* 这里的上下文表明了,一定存在一个节点的值大于等于指定范围的最小值,因此下一个节点一定不为null */ /* This is an inner range, so the next node cannot be NULL. */ final SkipListNode firstNodeGteMin = lastNodeLtMin.levelInfo[0].forward; assert firstNodeGteMin != null; /* 如果该节点的数据大于max,则不存在再范围内的节点 */ /* Check if score <= max. */ if (!zslValueLteMax(firstNodeGteMin.score, range)) { return null; } return firstNodeGteMin; } /** * 找出最后一个在指定范围内的节点。如果没有符合的节点,则返回null。 *

* Find the last node that is contained in the specified range. * Returns NULL when no element is contained in the range. * * @param range 范围描述信息 * @return 不存在返回null */ @Nullable SkipListNode zslLastInRange(ZScoreRangeSpec range) { /* zset数据范围与指定范围没有交集,可提前返回,减少不必要的遍历 */ /* If everything is out of range, return early. */ if (!zslIsInRange(range)) { return null; } SkipListNode lastNodeLteMax = this.header; for (int i = this.level - 1; i >= 0; i--) { /* Go forward while *IN* range. */ while (lastNodeLteMax.levelInfo[i].forward != null && zslValueLteMax(lastNodeLteMax.levelInfo[i].forward.score, range)) { // 如果当前节点的后继节点仍然小于最大值,则继续前进 lastNodeLteMax = lastNodeLteMax.levelInfo[i].forward; } } /* 这里的上下文表明一定存在一个节点的值小于指定范围的最大值,因此当前节点一定不为null */ /* This is an inner range, so this node cannot be NULL. */ assert lastNodeLteMax != null; /* Check if score >= min. */ if (!zslValueGteMin(lastNodeLteMax.score, range)) { return null; } return lastNodeLteMax; } /** * 删除指定分数区间的所有节点。 * Note: 该方法引用了ZSet的哈希表视图,以便从哈希表中删除成员。 *

* Delete all the elements with score between min and max from the skiplist. * Min and max are inclusive, so a score >= min || score <= max is deleted. * Note that this function takes the reference to the hash table view of the * sorted set, in order to remove the elements from the hash table too. * * @param range 范围描述符 * @param dict 对象id到score的映射 * @return 删除的节点数量 */ int zslDeleteRangeByScore(ZScoreRangeSpec range, Long2ObjectMap dict) { final SkipListNode[] update = updateCache; final int realLength = this.level; try { int removed = 0; SkipListNode lastNodeLtMin = this.header; for (int i = this.level - 1; i >= 0; i--) { while (lastNodeLtMin.levelInfo[i].forward != null && !zslValueGteMin(lastNodeLtMin.levelInfo[i].forward.score, range)) { lastNodeLtMin = lastNodeLtMin.levelInfo[i].forward; } update[i] = lastNodeLtMin; } /* 当前节点是小于目标范围最小值的最后一个节点,它的下一个节点可能为null,或大于等于最小值 */ /* Current node is the last with score < or <= min. */ SkipListNode firstNodeGteMin = lastNodeLtMin.levelInfo[0].forward; /* 删除在范围内的节点(小于等于最大值的节点) */ /* Delete nodes while in range. */ while (firstNodeGteMin != null && zslValueLteMax(firstNodeGteMin.score, range)) { final SkipListNode next = firstNodeGteMin.levelInfo[0].forward; zslDeleteNode(firstNodeGteMin, update); dict.remove(firstNodeGteMin.obj); removed++; firstNodeGteMin = next; } return removed; } finally { ZSetUtils.releaseUpdate(update, realLength); } } /** * 删除指定排名区间的所有成员。包括start和end。 * Note: start和end基于从1开始 *

* Delete all the elements with rank between start and end from the skiplist. * Start and end are inclusive. Note that start and end need to be 1-based * * @param start 起始排名 inclusive * @param end 截止排名 inclusive * @param dict member -> score的字典 * @return 删除的成员数量 */ int zslDeleteRangeByRank(int start, int end, Long2ObjectMap dict) { final SkipListNode[] update = updateCache; final int realLength = this.level; try { /* 已遍历的真实成员数量,表示成员的真实排名 */ int traversed = 0; int removed = 0; SkipListNode lastNodeLtStart = this.header; for (int i = this.level - 1; i >= 0; i--) { while (lastNodeLtStart.levelInfo[i].forward != null && (traversed + lastNodeLtStart.levelInfo[i].span) < start) { // 下一个节点的排名还未到范围内,继续前进 traversed += lastNodeLtStart.levelInfo[i].span; lastNodeLtStart = lastNodeLtStart.levelInfo[i].forward; } update[i] = lastNodeLtStart; } traversed++; /* levelInfo[0] 最下面一层就是要删除节点的直接前驱 */ SkipListNode firstNodeGteStart = lastNodeLtStart.levelInfo[0].forward; while (firstNodeGteStart != null && traversed <= end) { final SkipListNode next = firstNodeGteStart.levelInfo[0].forward; zslDeleteNode(firstNodeGteStart, update); dict.remove(firstNodeGteStart.obj); removed++; traversed++; firstNodeGteStart = next; } return removed; } finally { ZSetUtils.releaseUpdate(update, realLength); } } /** * 删除指定排名的成员 - 批量删除比单个删除更快捷 * (该方法非原生方法) * * @param rank 排名 1-based * @param dict member -> score的字典 * @return 删除的节点 */ SkipListNode zslDeleteByRank(int rank, Long2ObjectMap dict) { final SkipListNode[] update = updateCache; final int realLength = this.level; try { int traversed = 0; SkipListNode lastNodeLtStart = this.header; for (int i = this.level - 1; i >= 0; i--) { while (lastNodeLtStart.levelInfo[i].forward != null && (traversed + lastNodeLtStart.levelInfo[i].span) < rank) { // 下一个节点的排名还未到范围内,继续前进 traversed += lastNodeLtStart.levelInfo[i].span; lastNodeLtStart = lastNodeLtStart.levelInfo[i].forward; } update[i] = lastNodeLtStart; } /* levelInfo[0] 最下面一层就是要删除节点的直接前驱 */ final SkipListNode targetRankNode = lastNodeLtStart.levelInfo[0].forward; if (null != targetRankNode) { zslDeleteNode(targetRankNode, update); dict.remove(targetRankNode.obj); return targetRankNode; } else { return null; } } finally { ZSetUtils.releaseUpdate(update, realLength); } } /** * 通过score和key查找成员所属的排名。 * 如果找不到对应的成员,则返回0。 * Note:排名从1开始 *

* Find the rank for an element by both score and key. * Returns 0 when the element cannot be found, rank otherwise. * Note that the rank is 1-based due to the span of zsl->header to the * first element. * * @param score 节点分数 * @param obj 节点对应的数据id * @return 排名,从1开始 */ int zslGetRank(S score, long obj) { int rank = 0; SkipListNode firstNodeGteScore = this.header; for (int i = this.level - 1; i >= 0; i--) { while (firstNodeGteScore.levelInfo[i].forward != null && compareScoreAndObj(firstNodeGteScore.levelInfo[i].forward, score, obj) <= 0) { // <= 也继续前进,也就是我们期望在目标节点停下来,这样rank也不必特殊处理 rank += firstNodeGteScore.levelInfo[i].span; firstNodeGteScore = firstNodeGteScore.levelInfo[i].forward; } /* firstNodeGteScore might be equal to zsl->header, so test if firstNodeGteScore is header */ if (firstNodeGteScore != this.header && objEquals(firstNodeGteScore.obj, obj)) { // 可能在任意层找到 return rank; } } return 0; } /** * 查找指定排名的成员数据,如果不存在,则返回Null。 * 注意:排名从1开始 *

* Finds an element by its rank. The rank argument needs to be 1-based. * * @param rank 排名,1开始 * @return element */ @Nullable SkipListNode zslGetElementByRank(int rank) { int traversed = 0; SkipListNode firstNodeGteRank = this.header; for (int i = this.level - 1; i >= 0; i--) { while (firstNodeGteRank.levelInfo[i].forward != null && (traversed + firstNodeGteRank.levelInfo[i].span) <= rank) { // <= rank 表示我们期望在目标节点停下来 traversed += firstNodeGteRank.levelInfo[i].span; firstNodeGteRank = firstNodeGteRank.levelInfo[i].forward; } if (traversed == rank) { // 可能在任意层找到该排名的数据 return firstNodeGteRank; } } return null; } /** * @return 跳表中的成员数量 */ private int length() { return length; } /** * 创建一个skipList的节点 * * @param level 节点的高度 * @param score 成员分数 * @param obj 成员id * @return node */ private static SkipListNode zslCreateNode(int level, S score, long obj) { final SkipListNode node = new SkipListNode<>(obj, score, new SkipListLevel[level]); for (int index = 0; index < level; index++) { node.levelInfo[index] = new SkipListLevel<>(); } return node; } /** * 计算两个score的和 */ private S sum(S score1, S score2) { return scoreHandler.sum(score1, score2); } /** * @param start 起始分数 * @param end 截止分数 * @return spec */ private ZScoreRangeSpec newRangeSpec(S start, S end) { return newRangeSpec(start, false, end, false); } /** * @param rangeSpec 开放给用户的范围描述信息 * @return spec */ private ZScoreRangeSpec newRangeSpec(ScoreRangeSpec rangeSpec) { return newRangeSpec(rangeSpec.getStart(), rangeSpec.isStartEx(), rangeSpec.getEnd(), rangeSpec.isEndEx()); } /** * @param start 起始分数 * @param startEx 是否去除起始分数 * @param end 截止分数 * @param endEx 是否去除截止分数 * @return spec */ private ZScoreRangeSpec newRangeSpec(S start, boolean startEx, S end, boolean endEx) { if (compareScore(start, end) <= 0) { return new ZScoreRangeSpec<>(start, startEx, end, endEx); } else { return new ZScoreRangeSpec<>(end, endEx, start, startEx); } } /** * 值是否大于等于下限 * * @param value 要比较的score * @param spec 范围描述信息 * @return true/false */ @SuppressWarnings("BooleanMethodIsAlwaysInverted") boolean zslValueGteMin(S value, ZScoreRangeSpec spec) { return spec.minex ? compareScore(value, spec.min) > 0 : compareScore(value, spec.min) >= 0; } /** * 值是否小于等于上限 * * @param value 要比较的score * @param spec 范围描述信息 * @return true/false */ boolean zslValueLteMax(S value, ZScoreRangeSpec spec) { return spec.maxex ? compareScore(value, spec.max) < 0 : compareScore(value, spec.max) <= 0; } /** * 比较score和key的大小,分数作为第一排序条件,然后,相同分数的成员按照字典规则相对排序 * * @param forward 后继节点 * @param score 分数 * @param obj 成员的键 * @return 0 表示equals */ private int compareScoreAndObj(SkipListNode forward, S score, long obj) { final int scoreCompareR = compareScore(forward.score, score); if (scoreCompareR != 0) { return scoreCompareR; } return compareObj(forward.obj, obj); } /** * 比较两个成员的key,必须保证当且仅当两个键相等的时候返回0 * * @return 0表示相等 */ private int compareObj(long objA, long objB) { return objComparator.compare(objA, objB); } /** * 判断两个对象是否相等 * * @return true/false * @apiNote 使用compare == 0判断相等 */ private boolean objEquals(long objA, long objB) { // 不使用equals,而是使用compare return compareObj(objA, objB) == 0; } /** * 比较两个分数的大小 * * @return 0表示相等 */ private int compareScore(S score1, S score2) { return scoreHandler.compare(score1, score2); } /** * 判断第一个分数是否和第二个分数相等 * * @return true/false * @apiNote 使用compare == 0判断相等 */ private boolean scoreEquals(S score1, S score2) { return compareScore(score1, score2) == 0; } /** * 获取跳表的堆内存视图 * * @return string */ String dump() { final StringBuilder sb = new StringBuilder("{level = 0, nodeArray:[\n"); SkipListNode curNode = this.header.directForward(); int rank = 0; while (curNode != null) { sb.append("{rank:").append(rank++) .append(",obj:").append(curNode.obj) .append(",score:").append(curNode.score); curNode = curNode.directForward(); if (curNode != null) { sb.append("},\n"); } else { sb.append("}\n"); } } return sb.append("]}").toString(); } } /** * 跳表节点 */ private static class SkipListNode implements Long2ObjectEntry { /** * 节点对应的数据id */ final long obj; /** * 该节点数据对应的评分 - 如果要通用的话,这里将来将是一个泛型对象,需要实现{@link Comparable}。 */ final S score; /** * 该节点的层级信息 * level[]存放指向各层链表后一个节点的指针(后向指针)。 */ final SkipListLevel[] levelInfo; /** * 该节点的前向指针 * NOTE:(不包含header) * backward字段是指向链表前一个节点的指针(前向指针)。 * 节点只有1个前向指针,所以只有第1层链表是一个双向链表。 */ SkipListNode backward; private SkipListNode(long obj, S score, SkipListLevel[] levelInfo) { this.obj = obj; this.score = score; // noinspection unchecked this.levelInfo = levelInfo; } /** * @return 该节点的直接后继节点 */ SkipListNode directForward() { return levelInfo[0].forward; } @Override public long getLongMember() { return obj; } @Override public S getScore() { return score; } } /** * 跳表层级 */ private static class SkipListLevel { /** * 每层对应1个后向指针 (后继节点) */ SkipListNode forward; /** * 到后继节点之间的跨度 * 它表示当前的指针跨越了多少个节点。span用于计算成员排名(rank),这是Redis对于SkipList做的一个扩展。 */ int span; } // region 迭代 /** * ZSet迭代器 * Q: 为什么不写在{@link SkipList}中? * A: 因为删除数据需要访问{@link #dict}。 */ private class ZSetItr implements Iterator> { private SkipListNode lastReturned; private SkipListNode next; int expectedModCount = zsl.modCount; ZSetItr(SkipListNode next) { this.next = next; } @Override public boolean hasNext() { return next != null; } @Override public Long2ObjectEntry next() { checkForComodification(); if (next == null) { throw new NoSuchElementException(); } lastReturned = next; next = next.directForward(); return nextMember(lastReturned); } protected Long2ObjectEntry nextMember(SkipListNode lastReturned) { return new ZSetEntry<>(lastReturned.obj, lastReturned.score); } @Override public void remove() { if (lastReturned == null) { throw new IllegalStateException(); } checkForComodification(); // remove lastReturned dict.remove(lastReturned.obj); zsl.zslDelete(lastReturned.score, lastReturned.obj); // reset lastReturned lastReturned = null; expectedModCount = zsl.modCount; } final void checkForComodification() { if (zsl.modCount != expectedModCount) { throw new ConcurrentModificationException(); } } } private class FastZSetItr extends ZSetItr { FastZSetItr(SkipListNode next) { super(next); } @Override protected Long2ObjectEntry nextMember(SkipListNode lastReturned) { return lastReturned; } } public static class ZSetEntry implements Long2ObjectEntry { private final long member; private final S score; ZSetEntry(long member, S score) { this.member = member; this.score = score; } @Override public long getLongMember() { return member; } @Override public S getScore() { return score; } @Override public String toString() { return "{" + "member=" + member + ", score=" + score + '}'; } } // endregion } ================================================ FILE: gamioo-redis/src/main/java/io/gamioo/redis/zset/object2long/LongScoreHandler.java ================================================ /* * Copyright 2019 wjybxx * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to iBn writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.redis.zset.object2long; /** * long类型的score处理器 * * @author wjybxx * @version 1.0 * date - 2019/11/7 */ public interface LongScoreHandler { /** * 比较两个分数的大小 * Q: 为什么需要这个方法? * A: 想实现实现逆序 或 当你的score是由多个部分组合而成的时候,那么你就需要它。 * * @param score1 分数1 * @param score2 分数2 * @return 返回比较值 */ int compare(long score1, long score2); /** * 计算两个score的和 * Q: 为什么需要这个方法? * A: 当你的score是由多个部分组合而成的时候,那么你就需要它。 * * @param oldScore 当前分数 * @param increment 自定义增量 * @return sum */ long sum(long oldScore, long increment); } ================================================ FILE: gamioo-redis/src/main/java/io/gamioo/redis/zset/object2long/LongScoreHandlers.java ================================================ /* * Copyright 2019 wjybxx * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to iBn writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.redis.zset.object2long; /** * 基础分数比较器 * * @author wjybxx * @version 1.0 * date - 2019/11/7 */ public class LongScoreHandlers { private LongScoreHandlers() { } /** * @return Long类型的score处理器 */ public static LongScoreHandler scoreHandler() { return scoreHandler(false); } /** * 获取一个分数比较器 * * @param desc 是否降序 * @return 分数比较器 */ public static LongScoreHandler scoreHandler(boolean desc) { return desc ? DescScoreHandler.INSTANCE : AscScoreHandler.INSTANCE; } /** * 升序比较器 */ private static class AscScoreHandler implements LongScoreHandler { private static AscScoreHandler INSTANCE = new AscScoreHandler(); @Override public int compare(long score1, long score2) { return Long.compare(score1, score2); } @Override public long sum(long oldScore, long increment) { return oldScore + increment; } } /** * 降序比较器 */ private static class DescScoreHandler implements LongScoreHandler { private static DescScoreHandler INSTANCE = new DescScoreHandler(); @Override public int compare(long score1, long score2) { return Long.compare(score2, score1); } @Override public long sum(long oldScore, long increment) { return oldScore + increment; } } } ================================================ FILE: gamioo-redis/src/main/java/io/gamioo/redis/zset/object2long/LongScoreRangeSpec.java ================================================ /* * Copyright 2019 wjybxx * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to iBn writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.redis.zset.object2long; /** * Score范围区间 * * @author wjybxx * @version 1.0 * date - 2019/11/7 */ public class LongScoreRangeSpec { /** * 起始分数 - 最高分或最低分 */ private final long start; /** * 是否去除起始分数 * exclusive */ private final boolean startEx; /** * 截止分数 - 最高分或最低分 */ private final long end; /** * 是否去除最高分 * exclusive */ private final boolean endEx; public LongScoreRangeSpec(long start, long end) { this(start, false, end, false); } public LongScoreRangeSpec(long start, boolean startEx, long end, boolean endEx) { this.start = start; this.startEx = startEx; this.end = end; this.endEx = endEx; } public long getStart() { return start; } public boolean isStartEx() { return startEx; } public long getEnd() { return end; } public boolean isEndEx() { return endEx; } } ================================================ FILE: gamioo-redis/src/main/java/io/gamioo/redis/zset/object2long/Object2LongEntry.java ================================================ /* * Copyright 2019 wjybxx * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to iBn writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.redis.zset.object2long; import io.gamioo.redis.zset.generic.Entry; public interface Object2LongEntry extends Entry { @Override K getMember(); long getLongScore(); @Deprecated @Override default Long getScore() { return getLongScore(); } } ================================================ FILE: gamioo-redis/src/main/java/io/gamioo/redis/zset/object2long/Object2LongZSet.java ================================================ /* * Copyright 2019 wjybxx * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to iBn writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.gamioo.redis.zset.object2long; import io.gamioo.redis.zset.ZSetUtils; import io.gamioo.redis.zset.generic.Entry; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.concurrent.NotThreadSafe; import java.util.*; import static io.gamioo.redis.zset.ZSetUtils.ZSKIPLIST_MAXLEVEL; /** * key为泛型,score为long类型的sorted set - 参考redis的zset实现 * 排序规则 * 有序集合里面的成员是不能重复的,都是唯一的,但是,不同成员间有可能有相同的分数。 * 当多个成员有相同的分数时,它们将按照键排序。 * 即:分数作为第一排序条件,键作为第二排序条件,当分数相同时,比较键的大小。 *

* NOTE: * 1. ZSET中的排名从0开始(提供给用户的接口,排名都从0开始) * 2. ZSET使用键的compare结果判断两个键是否相等,而不是equals方法,因此必须保证键不同时compare结果一定不为0。 * 3. 又由于key需要存放于{@link HashMap}中,因此“相同”的key必须有相同的hashCode,且equals方法返回true。 * 手动加粗:key的关键属性最好是number或string且是final的 *

* 4. 我们允许zset中的成员是降序排列的-{@link LongScoreHandler}决定,可以更好的支持根据score降序的排行榜, * 而不是强迫你总是调用反转系列接口{@code zrev...},那样的设计不符合人的正常思维,就很容易出错。 *

* 5. 我们修改了redis中根据min和max查找和删除成员的接口,修改为start和end,当根据score范围查找或删除元素时,并不要求start小于等于end,我们会处理它们的大小关系。
* Q: 为什么要这么改动呢?
* A: 举个栗子:假如ScoreHandler比较两个long类型的score是逆序的,现在要删除排行榜中 1-10000分的成员,如果方法告诉你要传入的的是min和max, * 你会很自然的传入想到 (1,10000) 而不是 (10000,1)。因此,如果接口不做调整,这个接口就太反人类了,谁用都得错。 * *

* 这里只实现了redis zset中的几个常用的接口,扩展不是太麻烦,可以自己根据需要实现。 * * @param the type of key * @author wjybxx * @version 1.0 * date - 2019/11/4 */ @NotThreadSafe public class Object2LongZSet implements Iterable> { /** * member -> score */ private final Map dict = new HashMap<>(ZSetUtils.INIT_CAPACITY); private final SkipList zsl; private Object2LongZSet(Comparator keyComparator, LongScoreHandler scoreHandler) { this.zsl = new SkipList<>(keyComparator, scoreHandler); } /** * 创建一个键为string类型的zset * * @param scoreHandler score比较器,默认实现见{@link LongScoreHandlers} * @return zset */ public static Object2LongZSet newStringKeyZSet(LongScoreHandler scoreHandler) { return new Object2LongZSet<>(String::compareTo, scoreHandler); } /** * 创建一个键为long类型的zset * * @param scoreHandler score比较器,默认实现见{@link LongScoreHandlers} * @return zset */ public static Object2LongZSet newLongKeyZSet(LongScoreHandler scoreHandler) { return new Object2LongZSet<>(Long::compareTo, scoreHandler); } /** * 创建一个键为int类型的zset * * @param scoreHandler score比较器,默认实现见{@link LongScoreHandlers} * @return zset */ public static Object2LongZSet newIntKeyZSet(LongScoreHandler scoreHandler) { return new Object2LongZSet<>(Integer::compareTo, scoreHandler); } /** * 创建一个自定义键类型的zset * * @param keyComparator 键比较器,当score比较结果相等时,比较key - 注意:比较结果必须与key对象的状态改变无关。 * 请仔细阅读类文档中的注意事项。 * @param scoreHandler score比较器,默认实现见{@link LongScoreHandlers} * @param 键的类型 * @return zset */ public static Object2LongZSet newGenericKeyZSet(Comparator keyComparator, LongScoreHandler scoreHandler) { return new Object2LongZSet<>(keyComparator, scoreHandler); } // -------------------------------------------------------- insert ----------------------------------------------- /** * 往有序集合中新增一个成员。 * 如果指定添加的成员已经是有序集合里面的成员,则会更新成员的分数(score)并更新到正确的排序位置。 * * @param score 数据的评分 * @param member 成员id */ public void zadd(final long score, @Nonnull final K member) { Objects.requireNonNull(member); final Long oldScore = dict.put(member, score); if (oldScore != null) { // Q: 为何不再判断分数相等? // A: 这里假定分数相等的情况很少出现,可减少大量无用的判断 zsl.zslDelete(oldScore, member); } zsl.zslInsert(score, member); } /** * 往有序集合中新增一个成员。当且仅当该成员不在有序集合时才添加。 * * @param score 数据的评分 * @param member 成员id * @return 添加成功则返回true,否则返回false。 */ public boolean zaddnx(final long score, @Nonnull final K member) { Objects.requireNonNull(member); final Long oldScore = dict.putIfAbsent(member, score); if (oldScore == null) { zsl.zslInsert(score, member); return true; } return false; } /** * 为有序集的成员member的score值加上增量increment,并更新到正确的排序位置。 * 如果有序集中不存在member,就在有序集中添加一个member,score是increment(就好像它之前的score是0) * * @param increment 自定义增量 * @param member 成员id * @return 更新后的值 */ public long zincrby(long increment, @Nonnull K member) { Objects.requireNonNull(member); final Long oldScore = dict.get(member); final long score = oldScore == null ? increment : zsl.sum(oldScore, increment); zadd(score, member); return score; } /** * 为有序集的成员member的score值加上增量increment,并更新到正确的排序位置。 * 如果有序集中不存在member,则放弃更新并返回0。 * * @param increment 自定义增量 * @param member 成员id * @return 更新后的值,如果更新失败,则返回0。 */ public long zincrbyxx(long increment, @Nonnull K member) { Objects.requireNonNull(member); final Long oldScore = dict.get(member); if (oldScore == null) { return 0; } final long score = zsl.sum(oldScore, increment); zadd(score, member); return score; } // -------------------------------------------------------- remove ----------------------------------------------- /** * 删除指定成员 * * @param member 成员id * @return 如果成员存在,则返回对应的score,否则返回null。 */ public Long zrem(@Nonnull K member) { Objects.requireNonNull(member); final Long oldScore = dict.remove(member); if (oldScore != null) { zsl.zslDelete(oldScore, member); return oldScore; } else { return null; } } // region 通过score删除成员 /** * 移除zset中所有score值介于start和end之间(包括等于start或end)的成员 * * @param start 起始分数 inclusive * @param end 截止分数 inclusive * @return 删除的成员数目 */ public int zremrangeByScore(long start, long end) { return zremrangeByScore(zsl.newRangeSpec(start, end)); } /** * 移除zset中所有score值在范围区间的成员 * * @param spec score范围区间 * @return 删除的成员数目 */ private int zremrangeByScore(@Nonnull LongScoreRangeSpec spec) { return zremrangeByScore(zsl.newRangeSpec(spec)); } /** * 移除zset中所有score值在范围区间的成员 * * @param spec score范围区间 * @return 删除的成员数目 */ private int zremrangeByScore(@Nonnull ZLongScoreRangeSpec spec) { return zsl.zslDeleteRangeByScore(spec, dict); } // endregion // region 通过排名删除成员 /** * 删除并返回有序集合中的第一个成员。 * - 不使用min和max,是因为score的比较方式是用户自定义的。 * * @return 如果不存在,则返回null */ @Nullable public Object2LongEntry zpopFirst() { return zremByRank(0); } /** * 删除并返回有序集合中的最后一个成员。 * - 不使用min和max,是因为score的比较方式是用户自定义的。 * * @return 如果不存在,则返回null */ @Nullable public Object2LongEntry zpopLast() { return zremByRank(zsl.length() - 1); } /** * 删除指定排名的成员 * * @param rank 排名 0-based * @return 删除成功则返回该排名对应的数据,否则返回null */ @Nullable public Object2LongEntry zremByRank(int rank) { if (rank < 0 || rank >= zsl.length()) { return null; } final SkipListNode delete = zsl.zslDeleteByRank(rank + 1, dict); assert null != delete; return new ZSetEntry<>(delete.obj, delete.score); } /** * 删除指定排名范围的全部成员,start和end都是从0开始的。 * 排名0表示分数最小的成员。 * start和end都可以是负数,此时它们表示从最高排名成员开始的偏移量,eg: -1表示最高排名的成员, -2表示第二高分的成员,以此类推。 *

* Time complexity: O(log(N))+O(M) with N being the number of elements in the sorted set * and M the number of elements removed by the operation * * @param start 起始排名 * @param end 截止排名 * @return 删除的成员数目 */ public int zremrangeByRank(int start, int end) { final int zslLength = zsl.length(); start = ZSetUtils.convertStartRank(start, zslLength); end = ZSetUtils.convertEndRank(end, zslLength); if (ZSetUtils.isRankRangeEmpty(start, end, zslLength)) { return 0; } return zsl.zslDeleteRangeByRank(start + 1, end + 1, dict); } // endregion // region 限制成员数量 /** * 删除zset中尾部多余的成员,将zset中的成员数量限制到count之内。 * 保留前面的count个数成员 * * @param count 剩余数量限制 * @return 删除的成员数量 */ public int zlimit(int count) { if (zsl.length() <= count) { return 0; } return zsl.zslDeleteRangeByRank(count + 1, zsl.length(), dict); } /** * 删除zset中头部多余的成员,将zset中的成员数量限制到count之内。 * - 保留后面的count个数成员 * * @param count 剩余数量限制 * @return 删除的成员数量 */ public int zrevlimit(int count) { if (zsl.length() <= count) { return 0; } return zsl.zslDeleteRangeByRank(1, zsl.length() - count, dict); } // endregion // -------------------------------------------------------- query ----------------------------------------------- /** * 返回有序集成员member的score值。 * 如果member成员不是有序集的成员,返回null - 这里返回任意的基础值都是不合理的,因此必须返回null。 * * @param member 成员id * @return score */ public Long zscore(@Nonnull K member) { Objects.requireNonNull(member); return dict.get(member); } /** * 返回有序集中成员member的排名。 *

* Time complexity: O(log(N)) *

* 与redis的区别:我们使用-1表示成员不存在,而不是返回null。 * * @param member 成员id * @return 如果存在该成员,则返回该成员的排名(0-based),否则返回-1 */ public int zrank(@Nonnull K member) { Objects.requireNonNull(member); final Long score = dict.get(member); if (score == null) { return -1; } // 0 < zslGetRank <= size return zsl.zslGetRank(score, member) - 1; } /** * 返回有序集中成员member的逆序排名。 *

* Time complexity: O(log(N)) *

* 与redis的区别:我们使用-1表示成员不存在,而不是返回null。 * * @param member 成员id * @return 如果存在该成员,则返回该成员的排名(0-based),否则返回-1 */ public int zrevrank(@Nonnull K member) { Objects.requireNonNull(member); final Long score = dict.get(member); if (score == null) { return -1; } // 0 < zslGetRank <= size return zsl.length() - zsl.zslGetRank(score, member); } /** * 获取指定排名的成员数据。 * * @param rank 排名 0-based * @return memver,如果不存在,则返回null */ public Object2LongEntry zmemberByRank(int rank) { if (rank < 0 || rank >= zsl.length()) { return null; } final SkipListNode node = zsl.zslGetElementByRank(rank + 1); assert null != node; return new ZSetEntry<>(node.obj, node.score); } /** * 获取指定逆序排名的成员数据。 * * @param rank 排名 0-based * @return memver,如果不存在,则返回null */ public Object2LongEntry zrevmemberByRank(int rank) { if (rank < 0 || rank >= zsl.length()) { return null; } final SkipListNode node = zsl.zslGetElementByRank(zsl.length() - rank); assert null != node; return new ZSetEntry<>(node.obj, node.score); } // region 通过分数查询 /** * 返回有序集合中的分数在start和end之间的所有成员(包括分数等于start或者end的成员)。 * * @param start 起始分数 inclusive * @param end 截止分数 inclusive * @return memberInfo */ public List> zrangeByScore(long start, long end) { return zrangeByScoreWithOptions(zsl.newRangeSpec(start, end), 0, -1, false); } /** * 返回有序集合中的分数在指定范围区间的所有成员。 * * @param spec 范围描述信息 * @return memberInfo */ public List> zrangeByScore(LongScoreRangeSpec spec) { return zrangeByScoreWithOptions(zsl.newRangeSpec(spec), 0, -1, false); } /** * 返回有序集合中的分数在start和end之间的所有成员(包括分数等于start或者end的成员),返回的成员按照逆序排列。 * * @param start 起始分数 inclusive * @param end 截止分数 inclusive * @return memberInfo */ public List> zrevrangeByScore(final long start, final long end) { return zrangeByScoreWithOptions(zsl.newRangeSpec(start, end), 0, -1, true); } /** * 返回有序集合中的分数在指定范围之间的所有成员,返回的成员按照逆序排列。 * * @param rangeSpec score范围区间 * @return 删除的成员数目 */ public List> zrevrangeByScore(LongScoreRangeSpec rangeSpec) { return zrangeByScoreWithOptions(zsl.newRangeSpec(rangeSpec), 0, -1, true); } /** * 返回zset中指定分数区间内的成员,并按照指定顺序返回 * * @param rangeSpec score范围描述信息 * @param offset 偏移量(用于分页) 大于等于0 * @param limit 返回的成员数量(用于分页) 小于0表示不限制 * @param reverse 是否逆序 * @return memberInfo */ public List> zrangeByScoreWithOptions(final LongScoreRangeSpec rangeSpec, int offset, int limit, boolean reverse) { return zrangeByScoreWithOptions(zsl.newRangeSpec(rangeSpec), offset, limit, reverse); } /** * 返回zset中指定分数区间内的成员,并按照指定顺序返回 * * @param range score范围描述信息 * @param offset 偏移量(用于分页) 大于等于0 * @param limit 返回的成员数量(用于分页) 小于0表示不限制 * @param reverse 是否逆序 * @return memberInfo */ private List> zrangeByScoreWithOptions(final ZLongScoreRangeSpec range, int offset, int limit, boolean reverse) { if (offset < 0) { throw new IllegalArgumentException("offset" + ": " + offset + " (expected: >= 0)"); } SkipListNode listNode; /* If reversed, get the last node in range as starting point. */ if (reverse) { listNode = zsl.zslLastInRange(range); } else { listNode = zsl.zslFirstInRange(range); } /* No "first" element in the specified interval. */ if (listNode == null) { return new ArrayList<>(); } /* If there is an offset, just traverse the number of elements without * checking the score because that is done in the next loop. */ while (listNode != null && offset-- != 0) { if (reverse) { listNode = listNode.backward; } else { listNode = listNode.levelInfo[0].forward; } } final List> result = new ArrayList<>(); /* 这里使用 != 0 判断,当limit小于0时,表示不限制 */ while (listNode != null && limit-- != 0) { /* Abort when the node is no longer in range. */ if (reverse) { if (!zsl.zslValueGteMin(listNode.score, range)) { break; } } else { if (!zsl.zslValueLteMax(listNode.score, range)) { break; } } result.add(new ZSetEntry<>(listNode.obj, listNode.score)); /* Move to next node */ if (reverse) { listNode = listNode.backward; } else { listNode = listNode.levelInfo[0].forward; } } return result; } // endregion // region 通过排名查询 /** * 查询指定排名区间的成员信息 * * @param start 起始排名(0-based) inclusive * @param end 截止排名(0-based) inclusive * @return memberInfo */ public List> zrangeByRank(int start, int end) { return zrangeByRankInternal(start, end, false); } /** * 查询指定逆序排名区间的成员信息 * * @param start 起始排名(0-based) inclusive * @param end 截止排名(0-based) inclusive * @return memberInfo */ public List> zrevrangeByRank(int start, int end) { return zrangeByRankInternal(start, end, true); } /** * 查询指定排名区间的成员id和分数,start和end都是从0开始的。 * * @param start 起始排名(0-based) inclusive * @param end 截止排名(0-based) inclusive * @param reverse 是否逆序返回 * @return memberInfo */ private List> zrangeByRankInternal(int start, int end, boolean reverse) { final int zslLength = zsl.length(); start = ZSetUtils.convertStartRank(start, zslLength); end = ZSetUtils.convertEndRank(end, zslLength); if (ZSetUtils.isRankRangeEmpty(start, end, zslLength)) { return new ArrayList<>(); } int rangeLen = end - start + 1; SkipListNode listNode; /* start >= 0,大于0表示需要进行调整 */ /* Check if starting point is trivial, before doing log(N) lookup. */ if (reverse) { listNode = start > 0 ? zsl.zslGetElementByRank(zslLength - start) : zsl.tail; } else { listNode = start > 0 ? zsl.zslGetElementByRank(start + 1) : zsl.header.levelInfo[0].forward; } final List> result = new ArrayList<>(rangeLen); while (rangeLen-- > 0 && listNode != null) { result.add(new ZSetEntry<>(listNode.obj, listNode.score)); listNode = reverse ? listNode.backward : listNode.levelInfo[0].forward; } return result; } // endregion // region 统计分数人数 /** * 返回有序集key中,score值在指定区间(包括score值等于start或end)的成员 * * @param start 起始分数 * @param end 截止分数 * @return 分数区间段内的成员数量 */ public int zcount(long start, long end) { return zcountInternal(zsl.newRangeSpec(start, end)); } /** * 返回有序集key中,score值在指定区间的成员 * * @param rangeSpec score区间描述信息 * @return 分数区间段内的成员数量 */ public int zcount(LongScoreRangeSpec rangeSpec) { return zcountInternal(zsl.newRangeSpec(rangeSpec)); } /** * 返回有序集key中,score值在指定区间的成员 * * @param range score区间描述信息 * @return 分数区间段内的成员数量 */ private int zcountInternal(final ZLongScoreRangeSpec range) { final SkipListNode firstNodeInRange = zsl.zslFirstInRange(range); if (firstNodeInRange != null) { final int firstNodeRank = zsl.zslGetRank(firstNodeInRange.score, firstNodeInRange.obj); /* 如果firstNodeInRange不为null,那么lastNode也一定不为null(最坏的情况下firstNode就是lastNode) */ final SkipListNode lastNodeInRange = zsl.zslLastInRange(range); assert lastNodeInRange != null; final int lastNodeRank = zsl.zslGetRank(lastNodeInRange.score, lastNodeInRange.obj); return lastNodeRank - firstNodeRank + 1; } return 0; } /** * @return zset中的成员数量 */ public int zcard() { return zsl.length(); } // endregion // region 迭代 /** * 迭代有序集中的所有元素 * * @return iterator */ @Nonnull public Iterator> zscan() { return zscan(0); } /** * 从指定偏移量开始迭代有序集中的元素 * * @param offset 偏移量,如果小于等于0,则等价于{@link #zscan()} * @return iterator */ @Nonnull public Iterator> zscan(int offset) { if (offset <= 0) { return new ZSetItr(zsl.header.directForward()); } if (offset >= zsl.length()) { return new ZSetItr(null); } return new ZSetItr(zsl.zslGetElementByRank(offset + 1)); } @Nonnull @Override public Iterator> iterator() { return zscan(0); } /** * {@link Iterator#next()}总是返回相同的{@link Entry}对象,外部在迭代时不可以保持其引用。 * 改迭代器是为了避免创建大量的{@link Entry}对象。 * * @param offset 偏移量 * @return 返回entity对象 */ @Nonnull public Iterator> fastzscan(int offset) { if (offset <= 0) { return new FastZSetItr(zsl.header.directForward()); } if (offset >= zsl.length()) { return new FastZSetItr(null); } return new FastZSetItr(zsl.zslGetElementByRank(offset + 1)); } /** * @return 总是返回相同的{@link Entry}对象,外部在迭代时不可以保持其引用。 * 改迭代器是为了避免创建大量的{@link Entry}对象。 */ @Nonnull public Iterator> fastIterator() { return fastzscan(0); } // endregion /** * @return zset中当前的成员信息,用于测试 */ public String dump() { return zsl.dump(); } // ------------------------------------------------------- 内部实现 ---------------------------------------- /** * 跳表 * 注意:跳表的排名是从1开始的。 * * @author wjybxx * @version 1.0 * date - 2019/11/4 */ private static class SkipList { /** * 更新节点使用的缓存 - 避免频繁的申请空间 */ @SuppressWarnings("unchecked") private final SkipListNode[] updateCache = new SkipListNode[ZSKIPLIST_MAXLEVEL]; private final int[] rankCache = new int[ZSKIPLIST_MAXLEVEL]; private final Comparator objComparator; private final LongScoreHandler scoreHandler; /** * 修改次数 - 防止错误的迭代 */ private int modCount = 0; /** * 跳表头结点 - 哨兵 * 1. 可以简化判定逻辑 * 2. 恰好可以使得rank从1开始 */ private final SkipListNode header; /** * 跳表尾节点 */ private SkipListNode tail; /** * 跳表成员个数 * 注意:head头指针不包含在length计数中。 */ private int length = 0; /** * level表示SkipList的总层数,即所有节点层数的最大值。 */ private int level = 1; SkipList(Comparator objComparator, LongScoreHandler scoreHandler) { this.objComparator = objComparator; this.scoreHandler = scoreHandler; this.header = zslCreateNode(ZSKIPLIST_MAXLEVEL, 0, null); } /** * 插入一个新的节点到跳表。 * 这里假定成员已经不存在(直到调用方执行该方法)。 *

* zslInsert a new node in the skiplist. Assumes the element does not already * exist (up to the caller to enforce that). *

         *             header                    newNode
         *               _                                                 _
         * level - 1    |_| pre                                           |_|
         *  |           |_| pre                    _                      |_|
         *  |           |_| pre  _                |_|                     |_|
         *  |           |_|  ↓  |_| pre  _        |_|      _              |_|
         *  |           |_|     |_|  ↓  |_| pre   |_|     |_|             |_|
         *  |           |_|     |_|     |_| pre   |_|     |_|      _      |_|
         *  |           |_|     |_|     |_| pre   |_|     |_|     |_|     |_|
         *  0           |0|     |1|     |2| pre   |_|     |3|     |4|     |5|
         * 
* * @param score 分数 * @param obj obj 分数对应的成员id */ @SuppressWarnings("UnusedReturnValue") SkipListNode zslInsert(long score, K obj) { // 新节点的level final int level = ZSetUtils.zslRandomLevel(); // update - 需要更新后继节点的Node,新节点各层的前驱节点 // 1. 分数小的节点 // 2. 分数相同但id小的节点(分数相同时根据数据排序) // rank - 新节点各层前驱的当前排名 // 这里不必创建一个ZSKIPLIST_MAXLEVEL长度的数组,它取决于插入节点后的新高度,你在别处看见的代码会造成大量的空间浪费,增加GC压力。 // 如果创建的都是ZSKIPLIST_MAXLEVEL长度的数组,那么应该实现缓存 final SkipListNode[] update = updateCache; final int[] rank = rankCache; final int realLength = Math.max(level, this.level); try { // preNode - 新插入节点的前驱节点 SkipListNode preNode = header; for (int i = this.level - 1; i >= 0; i--) { /* store rank that is crossed to reach the insert position */ if (i == (this.level - 1)) { // 起始点,也就是head,它的排名就是0 rank[i] = 0; } else { // 由于是回溯降级继续遍历,因此其初始排名是前一次遍历的排名 rank[i] = rank[i + 1]; } while (preNode.levelInfo[i].forward != null && compareScoreAndObj(preNode.levelInfo[i].forward, score, obj) < 0) { // preNode的后继节点仍然小于要插入的节点,需要继续前进,同时累计排名 rank[i] += preNode.levelInfo[i].span; preNode = preNode.levelInfo[i].forward; } // 这是要插入节点的第i层的前驱节点,此时触发降级 update[i] = preNode; } if (level > this.level) { /* 新节点的层级大于当前层级,那么高出来的层级导致需要更新head,且排名和跨度是固定的 */ for (int i = this.level; i < level; i++) { rank[i] = 0; update[i] = this.header; update[i].levelInfo[i].span = this.length; } this.level = level; } /* 由于我们允许的重复score,并且zslInsert(该方法)的调用者在插入前必须测试要插入的member是否已经在hash表中。 * 因此我们假设key(obj)尚未被插入,并且重复插入score的情况永远不会发生。*/ /* we assume the key is not already inside, since we allow duplicated * scores, and the re-insertion of score and redis object should never * happen since the caller of zslInsert() should test in the hash table * if the element is already inside or not.*/ final SkipListNode newNode = zslCreateNode(level, score, obj); /* 这些节点的高度小于等于新插入的节点的高度,需要更新指针。此外它们当前的跨度被拆分了两部分,需要重新计算。 */ for (int i = 0; i < level; i++) { /* 链接新插入的节点 */ newNode.levelInfo[i].forward = update[i].levelInfo[i].forward; update[i].levelInfo[i].forward = newNode; /* rank[0] 是新节点的直接前驱的排名,每一层都有一个前驱,可以通过彼此的排名计算跨度 */ /* 计算新插入节点的跨度 和 重新计算所有前驱节点的跨度,之前的跨度被拆分为了两份*/ /* update span covered by update[i] as newNode is inserted here */ newNode.levelInfo[i].span = update[i].levelInfo[i].span - (rank[0] - rank[i]); update[i].levelInfo[i].span = (rank[0] - rank[i]) + 1; } /* 这些节点高于新插入的节点,它们的跨度可以简单的+1 */ /* increment span for untouched levels */ for (int i = level; i < this.level; i++) { update[i].levelInfo[i].span++; } /* 设置新节点的前向节点(回溯节点) - 这里不包含header,一定注意 */ newNode.backward = (update[0] == this.header) ? null : update[0]; /* 设置新节点的后向节点 */ if (newNode.levelInfo[0].forward != null) { newNode.levelInfo[0].forward.backward = newNode; } else { this.tail = newNode; } this.length++; this.modCount++; return newNode; } finally { ZSetUtils.releaseUpdate(update, realLength); ZSetUtils.releaseRank(rank, realLength); } } /** * Delete an element with matching score/object from the skiplist. * * @param score 分数用于快速定位节点 * @param obj 用于确定节点是否是对应的数据节点 */ @SuppressWarnings("UnusedReturnValue") boolean zslDelete(long score, K obj) { // update - 需要更新后继节点的Node // 1. 分数小的节点 // 2. 分数相同但id小的节点(分数相同时根据数据排序) final SkipListNode[] update = updateCache; final int realLength = this.level; try { SkipListNode preNode = this.header; for (int i = this.level - 1; i >= 0; i--) { while (preNode.levelInfo[i].forward != null && compareScoreAndObj(preNode.levelInfo[i].forward, score, obj) < 0) { // preNode的后继节点仍然小于要删除的节点,需要继续前进 preNode = preNode.levelInfo[i].forward; } // 这是目标节点第i层的可能前驱节点 update[i] = preNode; } /* 由于可能多个节点拥有相同的分数,因此必须同时比较score和object */ /* We may have multiple elements with the same score, what we need * is to find the element with both the right score and object. */ final SkipListNode targetNode = preNode.levelInfo[0].forward; if (targetNode != null && scoreEquals(targetNode.score, score) && objEquals(targetNode.obj, obj)) { zslDeleteNode(targetNode, update); return true; } /* not found */ return false; } finally { ZSetUtils.releaseUpdate(update, realLength); } } /** * Internal function used by zslDelete, zslDeleteByScore and zslDeleteByRank * * @param deleteNode 要删除的节点 * @param update 可能要更新的节点们 */ private void zslDeleteNode(final SkipListNode deleteNode, final SkipListNode[] update) { for (int i = 0; i < this.level; i++) { if (update[i].levelInfo[i].forward == deleteNode) { // 这些节点的高度小于等于要删除的节点,需要合并两个跨度 update[i].levelInfo[i].span += deleteNode.levelInfo[i].span - 1; update[i].levelInfo[i].forward = deleteNode.levelInfo[i].forward; } else { // 这些节点的高度高于要删除的节点,它们的跨度可以简单的 -1 update[i].levelInfo[i].span--; } } if (deleteNode.levelInfo[0].forward != null) { // 要删除的节点有后继节点 deleteNode.levelInfo[0].forward.backward = deleteNode.backward; } else { // 要删除的节点是tail节点 this.tail = deleteNode.backward; } // 如果删除的节点是最高等级的节点,则检查是否需要降级 if (deleteNode.levelInfo.length == this.level) { while (this.level > 1 && this.header.levelInfo[this.level - 1].forward == null) { // 如果最高层没有后继节点,则降级 this.level--; } } this.length--; this.modCount++; } /** * 判断zset中的数据所属的范围是否和指定range存在交集(intersection)。 * 它不代表zset存在指定范围内的数据。 * Returns if there is a part of the zset is in range. *
         *                         ZSet
         *              min ____________________ max
         *                 |____________________|
         *   min ______________ max  min _____________
         *      |______________|        |_____________|
         *          Range                   Range
         * 
* * @param range 范围描述信息 * @return true/false */ @SuppressWarnings("BooleanMethodIsAlwaysInverted") boolean zslIsInRange(ZLongScoreRangeSpec range) { if (isScoreRangeEmpty(range)) { // 传进来的范围为空 return false; } if (this.tail == null || !zslValueGteMin(this.tail.score, range)) { // 列表有序,按照从score小到大,如果尾部节点数据小于最小值,那么一定不在区间范围内 return false; } final SkipListNode firstNode = this.header.levelInfo[0].forward; if (firstNode == null || !zslValueLteMax(firstNode.score, range)) { // 列表有序,按照从score小到大,如果首部节点数据大于最大值,那么一定不在范围内 return false; } return true; } /** * 测试score范围信息是否为空(无效) * * @param range 范围描述信息 * @return true/false */ private boolean isScoreRangeEmpty(ZLongScoreRangeSpec range) { // 这里和redis有所区别,这里min一定小于等于max return scoreEquals(range.min, range.max) && (range.minex || range.maxex); } /** * 找出第一个在指定范围内的节点。如果没有符合的节点,则返回null。 *

* Find the first node that is contained in the specified range. * Returns NULL when no element is contained in the range. * * @param range 范围描述符 * @return 不存在返回null */ @Nullable SkipListNode zslFirstInRange(ZLongScoreRangeSpec range) { /* zset数据范围与指定范围没有交集,可提前返回,减少不必要的遍历 */ /* If everything is out of range, return early. */ if (!zslIsInRange(range)) { return null; } SkipListNode lastNodeLtMin = this.header; for (int i = this.level - 1; i >= 0; i--) { /* 前进直到出现后继节点大于等于指定最小值的节点 */ /* Go forward while *OUT* of range. */ while (lastNodeLtMin.levelInfo[i].forward != null && !zslValueGteMin(lastNodeLtMin.levelInfo[i].forward.score, range)) { // 如果当前节点的后继节点仍然小于指定范围的最小值,则继续前进 lastNodeLtMin = lastNodeLtMin.levelInfo[i].forward; } } /* 这里的上下文表明了,一定存在一个节点的值大于等于指定范围的最小值,因此下一个节点一定不为null */ /* This is an inner range, so the next node cannot be NULL. */ final SkipListNode firstNodeGteMin = lastNodeLtMin.levelInfo[0].forward; assert firstNodeGteMin != null; /* 如果该节点的数据大于max,则不存在再范围内的节点 */ /* Check if score <= max. */ if (!zslValueLteMax(firstNodeGteMin.score, range)) { return null; } return firstNodeGteMin; } /** * 找出最后一个在指定范围内的节点。如果没有符合的节点,则返回null。 *

* Find the last node that is contained in the specified range. * Returns NULL when no element is contained in the range. * * @param range 范围描述信息 * @return 不存在返回null */ @Nullable SkipListNode zslLastInRange(ZLongScoreRangeSpec range) { /* zset数据范围与指定范围没有交集,可提前返回,减少不必要的遍历 */ /* If everything is out of range, return early. */ if (!zslIsInRange(range)) { return null; } SkipListNode lastNodeLteMax = this.header; for (int i = this.level - 1; i >= 0; i--) { /* Go forward while *IN* range. */ while (lastNodeLteMax.levelInfo[i].forward != null && zslValueLteMax(lastNodeLteMax.levelInfo[i].forward.score, range)) { // 如果当前节点的后继节点仍然小于最大值,则继续前进 lastNodeLteMax = lastNodeLteMax.levelInfo[i].forward; } } /* 这里的上下文表明一定存在一个节点的值小于指定范围的最大值,因此当前节点一定不为null */ /* This is an inner range, so this node cannot be NULL. */ assert lastNodeLteMax != null; /* Check if score >= min. */ if (!zslValueGteMin(lastNodeLteMax.score, range)) { return null; } return lastNodeLteMax; } /** * 删除指定分数区间的所有节点。 * Note: 该方法引用了ZSet的哈希表视图,以便从哈希表中删除成员。 *

* Delete all the elements with score between min and max from the skiplist. * Min and max are inclusive, so a score >= min || score <= max is deleted. * Note that this function takes the reference to the hash table view of the * sorted set, in order to remove the elements from the hash table too. * * @param range 范围描述符 * @param dict 对象id到score的映射 * @return 删除的节点数量 */ int zslDeleteRangeByScore(ZLongScoreRangeSpec range, Map dict) { final SkipListNode[] update = updateCache; final int realLength = this.level; try { int removed = 0; SkipListNode lastNodeLtMin = this.header; for (int i = this.level - 1; i >= 0; i--) { while (lastNodeLtMin.levelInfo[i].forward != null && !zslValueGteMin(lastNodeLtMin.levelInfo[i].forward.score, range)) { lastNodeLtMin = lastNodeLtMin.levelInfo[i].forward; } update[i] = lastNodeLtMin; } /* 当前节点是小于目标范围最小值的最后一个节点,它的下一个节点可能为null,或大于等于最小值 */ /* Current node is the last with score < or <= min. */ SkipListNode firstNodeGteMin = lastNodeLtMin.levelInfo[0].forward; /* 删除在范围内的节点(小于等于最大值的节点) */ /* Delete nodes while in range. */ while (firstNodeGteMin != null && zslValueLteMax(firstNodeGteMin.score, range)) { final SkipListNode next = firstNodeGteMin.levelInfo[0].forward; zslDeleteNode(firstNodeGteMin, update); dict.remove(firstNodeGteMin.obj); removed++; firstNodeGteMin = next; } return removed; } finally { ZSetUtils.releaseUpdate(update, realLength); } } /** * 删除指定排名区间的所有成员。包括start和end。 * Note: start和end基于从1开始 *

* Delete all the elements with rank between start and end from the skiplist. * Start and end are inclusive. Note that start and end need to be 1-based * * @param start 起始排名 inclusive * @param end 截止排名 inclusive * @param dict member -> score的字典 * @return 删除的成员数量 */ int zslDeleteRangeByRank(int start, int end, Map dict) { final SkipListNode[] update = updateCache; final int realLength = this.level; try { /* 已遍历的真实成员数量,表示成员的真实排名 */ int traversed = 0; int removed = 0; SkipListNode lastNodeLtStart = this.header; for (int i = this.level - 1; i >= 0; i--) { while (lastNodeLtStart.levelInfo[i].forward != null && (traversed + lastNodeLtStart.levelInfo[i].span) < start) { // 下一个节点的排名还未到范围内,继续前进 traversed += lastNodeLtStart.levelInfo[i].span; lastNodeLtStart = lastNodeLtStart.levelInfo[i].forward; } update[i] = lastNodeLtStart; } traversed++; /* levelInfo[0] 最下面一层就是要删除节点的直接前驱 */ SkipListNode firstNodeGteStart = lastNodeLtStart.levelInfo[0].forward; while (firstNodeGteStart != null && traversed <= end) { final SkipListNode next = firstNodeGteStart.levelInfo[0].forward; zslDeleteNode(firstNodeGteStart, update); dict.remove(firstNodeGteStart.obj); removed++; traversed++; firstNodeGteStart = next; } return removed; } finally { ZSetUtils.releaseUpdate(update, realLength); } } /** * 删除指定排名的成员 - 批量删除比单个删除更快捷 * (该方法非原生方法) * * @param rank 排名 1-based * @param dict member -> score的字典 * @return 删除的节点 */ SkipListNode zslDeleteByRank(int rank, Map dict) { final SkipListNode[] update = updateCache; final int realLength = this.level; try { int traversed = 0; SkipListNode lastNodeLtStart = this.header; for (int i = this.level - 1; i >= 0; i--) { while (lastNodeLtStart.levelInfo[i].forward != null && (traversed + lastNodeLtStart.levelInfo[i].span) < rank) { // 下一个节点的排名还未到范围内,继续前进 traversed += lastNodeLtStart.levelInfo[i].span; lastNodeLtStart = lastNodeLtStart.levelInfo[i].forward; } update[i] = lastNodeLtStart; } /* levelInfo[0] 最下面一层就是要删除节点的直接前驱 */ final SkipListNode targetRankNode = lastNodeLtStart.levelInfo[0].forward; if (null != targetRankNode) { zslDeleteNode(targetRankNode, update); dict.remove(targetRankNode.obj); return targetRankNode; } else { return null; } } finally { ZSetUtils.releaseUpdate(update, realLength); } } /** * 通过score和key查找成员所属的排名。 * 如果找不到对应的成员,则返回0。 * Note:排名从1开始 *

* Find the rank for an element by both score and key. * Returns 0 when the element cannot be found, rank otherwise. * Note that the rank is 1-based due to the span of zsl->header to the * first element. * * @param score 节点分数 * @param obj 节点对应的数据id * @return 排名,从1开始 */ int zslGetRank(long score, @Nonnull K obj) { int rank = 0; SkipListNode firstNodeGteScore = this.header; for (int i = this.level - 1; i >= 0; i--) { while (firstNodeGteScore.levelInfo[i].forward != null && compareScoreAndObj(firstNodeGteScore.levelInfo[i].forward, score, obj) <= 0) { // <= 也继续前进,也就是我们期望在目标节点停下来,这样rank也不必特殊处理 rank += firstNodeGteScore.levelInfo[i].span; firstNodeGteScore = firstNodeGteScore.levelInfo[i].forward; } /* firstNodeGteScore might be equal to zsl->header, so test if firstNodeGteScore is header */ if (firstNodeGteScore != this.header && objEquals(firstNodeGteScore.obj, obj)) { // 可能在任意层找到 return rank; } } return 0; } /** * 查找指定排名的成员数据,如果不存在,则返回Null。 * 注意:排名从1开始 *

* Finds an element by its rank. The rank argument needs to be 1-based. * * @param rank 排名,1开始 * @return element */ @Nullable SkipListNode zslGetElementByRank(int rank) { int traversed = 0; SkipListNode firstNodeGteRank = this.header; for (int i = this.level - 1; i >= 0; i--) { while (firstNodeGteRank.levelInfo[i].forward != null && (traversed + firstNodeGteRank.levelInfo[i].span) <= rank) { // <= rank 表示我们期望在目标节点停下来 traversed += firstNodeGteRank.levelInfo[i].span; firstNodeGteRank = firstNodeGteRank.levelInfo[i].forward; } if (traversed == rank) { // 可能在任意层找到该排名的数据 return firstNodeGteRank; } } return null; } /** * @return 跳表中的成员数量 */ private int length() { return length; } /** * 创建一个skipList的节点 * * @param level 节点的高度 * @param score 成员分数 * @param obj 成员id * @return node */ private static SkipListNode zslCreateNode(int level, long score, K obj) { final SkipListNode node = new SkipListNode<>(obj, score, new SkipListLevel[level]); for (int index = 0; index < level; index++) { node.levelInfo[index] = new SkipListLevel<>(); } return node; } /** * 计算两个score的和 */ private long sum(long score1, long score2) { return scoreHandler.sum(score1, score2); } /** * @param start 起始分数 * @param end 截止分数 * @return spec */ private ZLongScoreRangeSpec newRangeSpec(long start, long end) { return newRangeSpec(start, false, end, false); } /** * @param rangeSpec 开放给用户的范围描述信息 * @return spec */ private ZLongScoreRangeSpec newRangeSpec(LongScoreRangeSpec rangeSpec) { return newRangeSpec(rangeSpec.getStart(), rangeSpec.isStartEx(), rangeSpec.getEnd(), rangeSpec.isEndEx()); } /** * @param start 起始分数 * @param startEx 是否去除起始分数 * @param end 截止分数 * @param endEx 是否去除截止分数 * @return spec */ private ZLongScoreRangeSpec newRangeSpec(long start, boolean startEx, long end, boolean endEx) { if (compareScore(start, end) <= 0) { return new ZLongScoreRangeSpec(start, startEx, end, endEx); } else { return new ZLongScoreRangeSpec(end, endEx, start, startEx); } } /** * 值是否大于等于下限 * * @param value 要比较的score * @param spec 范围描述信息 * @return true/false */ @SuppressWarnings("BooleanMethodIsAlwaysInverted") boolean zslValueGteMin(long value, ZLongScoreRangeSpec spec) { return spec.minex ? compareScore(value, spec.min) > 0 : compareScore(value, spec.min) >= 0; } /** * 值是否小于等于上限 * * @param value 要比较的score * @param spec 范围描述信息 * @return true/false */ boolean zslValueLteMax(long value, ZLongScoreRangeSpec spec) { return spec.maxex ? compareScore(value, spec.max) < 0 : compareScore(value, spec.max) <= 0; } /** * 比较score和key的大小,分数作为第一排序条件,然后,相同分数的成员按照字典规则相对排序 * * @param forward 后继节点 * @param score 分数 * @param obj 成员的键 * @return 0 表示equals */ private int compareScoreAndObj(SkipListNode forward, long score, K obj) { final int scoreCompareR = compareScore(forward.score, score); if (scoreCompareR != 0) { return scoreCompareR; } return compareObj(forward.obj, obj); } /** * 比较两个成员的key,必须保证当且仅当两个键相等的时候返回0 * 字符串带有这样的特性。 */ private int compareObj(@Nonnull K objA, @Nonnull K objB) { return objComparator.compare(objA, objB); } /** * 判断两个对象是否相等,必须保证当且仅当两个键相等的时候返回0 * 字符串带有这样的特性。 * * @return true/false * @apiNote 使用compare == 0判断相等 */ private boolean objEquals(K objA, K objB) { // 不使用equals,而是使用compare return compareObj(objA, objB) == 0; } /** * 比较两个分数的大小 * * @return 0表示相等 */ private int compareScore(long score1, long score2) { return scoreHandler.compare(score1, score2); } /** * 判断第一个分数是否和第二个分数相等 * * @return true/false * @apiNote 使用compare == 0判断相等 */ private boolean scoreEquals(long score1, long score2) { return compareScore(score1, score2) == 0; } /** * 获取跳表的堆内存视图 * * @return string */ String dump() { final StringBuilder sb = new StringBuilder("{level = 0, nodeArray:[\n"); SkipListNode curNode = this.header.directForward(); int rank = 0; while (curNode != null) { sb.append("{rank:").append(rank++) .append(",obj:").append(curNode.obj) .append(",score:").append(curNode.score); curNode = curNode.directForward(); if (curNode != null) { sb.append("},\n"); } else { sb.append("}\n"); } } return sb.append("]}").toString(); } } /** * 跳表节点 */ private static class SkipListNode implements Object2LongEntry { /** * 节点对应的数据id */ final K obj; /** * 该节点数据对应的评分 - 如果要通用的话,这里将来将是一个泛型对象,需要实现{@link Comparable}。 */ final long score; /** * 该节点的层级信息 * level[]存放指向各层链表后一个节点的指针(后向指针)。 */ final SkipListLevel[] levelInfo; /** * 该节点的前向指针 * NOTE:(不包含header) * backward字段是指向链表前一个节点的指针(前向指针)。 * 节点只有1个前向指针,所以只有第1层链表是一个双向链表。 */ SkipListNode backward; private SkipListNode(K obj, long score, SkipListLevel[] levelInfo) { this.obj = obj; this.score = score; // noinspection unchecked this.levelInfo = levelInfo; } /** * @return 该节点的直接后继节点 */ SkipListNode directForward() { return levelInfo[0].forward; } @Override public K getMember() { return obj; } @Override public long getLongScore() { return score; } } /** * 跳表层级 */ private static class SkipListLevel { /** * 每层对应1个后向指针 (后继节点) */ SkipListNode forward; /** * 到后继节点之间的跨度 * 它表示当前的指针跨越了多少个节点。span用于计算成员排名(rank),这是Redis对于SkipList做的一个扩展。 */ int span; } // region 迭代 /** * ZSet迭代器 * Q: 为什么不写在{@link SkipList}中? * A: 因为删除数据需要访问{@link #dict}。 */ private class ZSetItr implements Iterator> { private SkipListNode lastReturned; private SkipListNode next; int expectedModCount = zsl.modCount; ZSetItr(SkipListNode next) { this.next = next; } @Override public boolean hasNext() { return next != null; } @Override public Object2LongEntry next() { checkForComodification(); if (next == null) { throw new NoSuchElementException(); } lastReturned = next; next = next.directForward(); return nextMember(lastReturned); } protected Object2LongEntry nextMember(SkipListNode lastReturned) { return new ZSetEntry<>(lastReturned.obj, this.lastReturned.score); } @Override public void remove() { if (lastReturned == null) { throw new IllegalStateException(); } checkForComodification(); // remove lastReturned dict.remove(lastReturned.obj); zsl.zslDelete(lastReturned.score, lastReturned.obj); // reset lastReturned lastReturned = null; expectedModCount = zsl.modCount; } final void checkForComodification() { if (zsl.modCount != expectedModCount) { throw new ConcurrentModificationException(); } } } private class FastZSetItr extends ZSetItr { FastZSetItr(SkipListNode next) { super(next); } @Override protected Object2LongEntry nextMember(SkipListNode lastReturned) { return lastReturned; } } public static class ZSetEntry implements Object2LongEntry { private final K member; private final long score; ZSetEntry(K member, long score) { this.member = member; this.score = score; } @Override public K getMember() { return member; } @Override public long getLongScore() { return score; } @Override public String toString() { return "{" + "member=" + member + ", score=" + score + '}'; } } // endregion } ================================================ FILE: gamioo-redis/src/main/java/io/gamioo/redis/zset/object2long/ZLongScoreRangeSpec.java ================================================ package io.gamioo.redis.zset.object2long; /** * {@link Object2LongZSet}中“score”范围描述信息 - specification模式 */ public final class ZLongScoreRangeSpec { /** * 最低分数 */ final long min; /** * 是否去除下限 * exclusive */ final boolean minex; /** * 最高分数 */ final long max; /** * 是否去除上限 * exclusive */ final boolean maxex; public ZLongScoreRangeSpec(long min, boolean minex, long max, boolean maxex) { this.min = min; this.max = max; this.minex = minex; this.maxex = maxex; } } ================================================ FILE: gamioo-redis/src/test/java/io/gamioo/redis/zset/generic/GenericZSetTest.java ================================================ package io.gamioo.redis.zset.generic; import java.util.concurrent.ThreadLocalRandom; import java.util.stream.LongStream; /** * {@link GenericZSet}的复杂score测试 * * @author wjybxx * @version 1.0 * date - 2019/11/7 * github - https://github.com/hl845740757 */ public class GenericZSetTest { public static void main(String[] args) { final GenericZSet zSet = GenericZSet.newLongKeyZSet(new ComplexScoreHandler()); // 插入数据 LongStream.rangeClosed(1, 10000).forEach(playerId -> { zSet.zadd(randomScore(), playerId); }); // 覆盖数据 LongStream.rangeClosed(1, 10000).forEach(playerId -> { zSet.zadd(randomScore(), playerId); }); System.out.println("------------------------- dump ----------------------"); System.out.println(zSet.dump()); System.out.println(); } private static ComplexScore randomScore() { return new ComplexScore(ThreadLocalRandom.current().nextInt(0, 4), ThreadLocalRandom.current().nextInt(1, 101), System.currentTimeMillis()); } private static class ComplexScore { private final int vipLevel; private final int level; private final long timestamp; ComplexScore(int vipLevel, int level, long timeStamp) { this.level = level; this.vipLevel = vipLevel; this.timestamp = timeStamp; } public int getLevel() { return level; } public int getVipLevel() { return vipLevel; } public long getTimestamp() { return timestamp; } @Override public String toString() { return "{" + "vipLevel=" + vipLevel + ", level=" + level + ", timestamp=" + timestamp + '}'; } } private static class ComplexScoreHandler implements ScoreHandler { @Override public int compare(ComplexScore o1, ComplexScore o2) { // vip等级排序 - 等级高的排前面(逆序) final int vipLevelCompareR = Integer.compare(o2.vipLevel, o1.vipLevel); if (vipLevelCompareR != 0) { return vipLevelCompareR; } // 普通等级排序 - 等级高的排前面(逆序) final int levelCompareR = Integer.compare(o2.level, o1.level); if (levelCompareR != 0) { return levelCompareR; } // 时间戳升序(时间戳小的排前面) return Long.compare(o1.timestamp, o2.timestamp); } @Override public ComplexScore sum(ComplexScore oldScore, ComplexScore increment) { throw new UnsupportedOperationException(); } } } ================================================ FILE: gamioo-redis/src/test/java/io/gamioo/redis/zset/long2object/Long2ObjectZSetTest.java ================================================ package io.gamioo.redis.zset.long2object; import io.gamioo.redis.zset.generic.ScoreHandler; import java.util.concurrent.ThreadLocalRandom; import java.util.stream.LongStream; /** * {@link Long2ObjectZSet}的测试用例 * * @author wjybxx * @version 1.0 * date - 2019/11/9 * github - https://github.com/hl845740757 */ public class Long2ObjectZSetTest { public static void main(String[] args) { final Long2ObjectZSet zSet = Long2ObjectZSet.newZSet(new ComplexScoreHandler()); // 插入数据 LongStream.rangeClosed(1, 10000).forEach(playerId -> { zSet.zadd(randomScore(), playerId); }); // 覆盖数据 LongStream.rangeClosed(1, 10000).forEach(playerId -> { zSet.zadd(randomScore(), playerId); }); System.out.println("------------------------- dump ----------------------"); System.out.println(zSet.dump()); System.out.println(); } private static ComplexScore randomScore() { return new ComplexScore(ThreadLocalRandom.current().nextInt(0, 4), ThreadLocalRandom.current().nextInt(1, 101), System.currentTimeMillis()); } private static class ComplexScore { private final int vipLevel; private final int level; private final long timestamp; ComplexScore(int vipLevel, int level, long timeStamp) { this.level = level; this.vipLevel = vipLevel; this.timestamp = timeStamp; } public int getLevel() { return level; } public int getVipLevel() { return vipLevel; } public long getTimestamp() { return timestamp; } @Override public String toString() { return "{" + "vipLevel=" + vipLevel + ", level=" + level + ", timestamp=" + timestamp + '}'; } } private static class ComplexScoreHandler implements ScoreHandler { @Override public int compare(ComplexScore o1, ComplexScore o2) { // vip等级排序 - 等级高的排前面(逆序) final int vipLevelCompareR = Integer.compare(o2.vipLevel, o1.vipLevel); if (vipLevelCompareR != 0) { return vipLevelCompareR; } // 普通等级排序 - 等级高的排前面(逆序) final int levelCompareR = Integer.compare(o2.level, o1.level); if (levelCompareR != 0) { return levelCompareR; } // 时间戳升序(时间戳小的排前面) return Long.compare(o1.timestamp, o2.timestamp); } @Override public ComplexScore sum(ComplexScore oldScore, ComplexScore increment) { throw new UnsupportedOperationException(); } } } ================================================ FILE: gamioo-redis/src/test/java/io/gamioo/redis/zset/object2long/Object2LongZSetTest.java ================================================ package io.gamioo.redis.zset.object2long; import java.util.concurrent.ThreadLocalRandom; import java.util.stream.LongStream; /** * {@link Object2LongZSet}的复杂score测试 * 这里的score由vipLevel和level拼接而成,因此在比较时需要拆分。 * * @author wjybxx * @version 1.0 * date - 2019/11/7 * github - https://github.com/hl845740757 */ public class Object2LongZSetTest { private static final long MULTIPLE = 10000; public static void main(String[] args) { final Object2LongZSet zSet = Object2LongZSet.newLongKeyZSet(new ComplexScoreHandler()); // 插入数据 LongStream.rangeClosed(1, 10000).forEach(playerId -> { zSet.zadd(randomScore(), playerId); }); // 覆盖数据 LongStream.rangeClosed(1, 10000).forEach(playerId -> { zSet.zadd(randomScore(), playerId); }); System.out.println("------------------------- dump ----------------------"); System.out.println(zSet.dump()); System.out.println(); } private static long randomScore() { return composeToLong(ThreadLocalRandom.current().nextInt(0, 4), ThreadLocalRandom.current().nextInt(1, 101)); } private static long composeToLong(int a, int b) { return (long) a * MULTIPLE + b; } private static int vipLevel(long score) { return (int) (score / MULTIPLE); } private static int level(long score) { return (int) (score % MULTIPLE); } private static class ComplexScoreHandler implements LongScoreHandler { @Override public int compare(long o1, long o2) { // vip等级排序 - 等级高的排前面(逆序) final int vipLevelCompareR = Integer.compare(vipLevel(o2), vipLevel(o1)); if (vipLevelCompareR != 0) { return vipLevelCompareR; } // 普通等级排序 - 等级高的排前面(逆序) return Integer.compare(level(o2), level(o1)); } @Override public long sum(long oldScore, long increment) { throw new UnsupportedOperationException(); } } } ================================================ FILE: gamioo-sandbox/build.gradle ================================================ dependencies { implementation project(':gamioo-common'); implementation 'org.furyio:fury-core:0.1.0' implementation group: 'io.protostuff', name: 'protostuff-core', version: '1.8.0' implementation group: 'io.protostuff', name: 'protostuff-runtime', version: '1.8.0' implementation group: 'com.carrotsearch', name: 'java-sizeof', version: '0.0.5' testImplementation group: 'org.springframework.security', name: 'spring-security-crypto', version: '5.8.2' testImplementation 'org.tmatesoft.svnkit:svnkit:1.10.11' implementation 'com.github.houbb:data-factory-core:1.2.0' testImplementation('com.github.woostju:ansible-client:1.0.0-RELEASE') { exclude group: "ch.qos.logback", module: "logback-classic" } } ================================================ FILE: gamioo-sandbox/src/jmh/java/io/gamioo/sandbox/AsymmetricBenchMark.java ================================================ package io.gamioo.sandbox; import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; /** * JMH测试15:官方Asymmetric样例 * * @author Allen Jiang */ @Fork(1) @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.MICROSECONDS) @Warmup(iterations = 5, time = 1) @Measurement(iterations = 5, time = 1) @State(Scope.Group) public class AsymmetricBenchMark { private AtomicLong counter; @Setup public void up() { counter = new AtomicLong(); } @Benchmark @Group("atomic") @GroupThreads(3) public long inc() { return counter.incrementAndGet(); } @Benchmark @Group("atomic") @GroupThreads(1) public long get() { return counter.get(); } public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() .include(AsymmetricBenchMark.class.getSimpleName()) .build(); new Runner(opt).run(); } } ================================================ FILE: gamioo-sandbox/src/jmh/java/io/gamioo/sandbox/InterruptBenchmark.java ================================================ package io.gamioo.sandbox; import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import org.openjdk.jmh.runner.options.TimeValue; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.TimeUnit; /** * JMH测试16:Interrupts Benchmark样例 * * @author Allen Jiang */ @Fork(1) @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) @State(Scope.Group) public class InterruptBenchmark { private BlockingQueue queue; private final static int VALUE = Integer.MAX_VALUE; @Setup public void init() { this.queue = new ArrayBlockingQueue<>(10); } @GroupThreads(5) @Group("queue") @Warmup(iterations = 5, time = 1) @Measurement(iterations = 5, time = 1) @Benchmark public void put() throws InterruptedException { this.queue.put(VALUE); } @GroupThreads(5) @Group("queue") @Warmup(iterations = 5, time = 1) @Measurement(iterations = 5, time = 1) @Benchmark public int take() throws InterruptedException { return this.queue.take(); } public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() .include(InterruptBenchmark.class.getSimpleName()) // 将每个批次的超时时间设置为10秒 .timeout(TimeValue.milliseconds(10000)) .build(); new Runner(opt).run(); } } ================================================ FILE: gamioo-sandbox/src/jmh/java/io/gamioo/sandbox/MapBenchMark.java ================================================ package io.gamioo.sandbox; import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import java.util.Collections; import java.util.HashMap; import java.util.Hashtable; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.TimeUnit; /** * JMH测试16:测试线程安全的几个Map的读写性能 * * @author Allen Jiang */ @Fork(1) @BenchmarkMode(Mode.Throughput) @Warmup(iterations = 5, time = 1) @Measurement(iterations = 5, time = 1) @OutputTimeUnit(TimeUnit.SECONDS) @State(Scope.Group) public class MapBenchMark { @Param({"ConcurrentHashMap", "ConcurrentSkipListMap", "Hashtable", "Collections.synchronizedMap"}) private String type; private Map map; @Setup public void setUp() { switch (type) { case "ConcurrentHashMap": this.map = new ConcurrentHashMap<>(); break; case "ConcurrentSkipListMap": this.map = new ConcurrentSkipListMap<>(); break; case "Hashtable": this.map = new Hashtable<>(); break; case "Collections.synchronizedMap": this.map = Collections.synchronizedMap( new HashMap<>()); break; default: throw new IllegalArgumentException("Illegal map type."); } } @Group("map") @GroupThreads(5) @Benchmark public void putMap() { int random = randomIntValue(); this.map.put(random, random); } @Group("map") @GroupThreads(5) @Benchmark public Integer getMap() { return this.map.get(randomIntValue()); } /** * 计算一个随机值用作Map中的Key和Value * * @return */ private int randomIntValue() { return (int) Math.ceil(Math.random() * 600000); } public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() .include(MapBenchMark.class.getSimpleName()) .build(); new Runner(opt).run(); } } ================================================ FILE: gamioo-sandbox/src/jmh/java/io/gamioo/sandbox/ProtoDeserializeBenchMark.java ================================================ package io.gamioo.sandbox; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONB; import com.alibaba.fastjson2.JSONReader; import com.alibaba.fastjson2.JSONWriter; import com.carrotsearch.sizeof.RamUsageEstimator; import com.github.houbb.data.factory.core.util.DataUtil; import io.fury.Fury; import io.fury.Language; import io.gamioo.common.util.FileUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.profile.CompilerProfiler; import org.openjdk.jmh.profile.GCProfiler; import org.openjdk.jmh.results.format.ResultFormatType; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import org.openjdk.jmh.runner.options.VerboseMode; import java.util.concurrent.TimeUnit; /** * @author Allen Jiang */ @Fork(1) @Warmup(iterations = 10, time = 1) @Measurement(iterations = 10, time = 1) @OutputTimeUnit(TimeUnit.SECONDS) @BenchmarkMode({Mode.Throughput}) @State(Scope.Benchmark) public class ProtoDeserializeBenchMark { private static final Logger logger = LogManager.getLogger(ProtoDeserializeBenchMark.class); private SkillFire_S2C_Msg skillFire_s2C_msg = DataUtil.build(SkillFire_S2C_Msg.class); private Fury fury; private Fury furyX; private byte[] furyArray; private byte[] furyArrayX; private byte[] protoArray; private byte[] jsonArray; private byte[] jsonArrayWithBeanToArray; @Setup public void init() { try { byte[] array = FileUtils.getByteArrayFromFile("message.txt"); skillFire_s2C_msg= JSON.parseObject(array,SkillFire_S2C_Msg.class); logger.info(skillFire_s2C_msg); } catch (Exception e) { logger.error(e.getMessage(), e); } fury = Fury.builder().withLanguage(Language.JAVA) .withRefTracking(true).requireClassRegistration(false).build(); furyX = Fury.builder().withLanguage(Language.JAVA) .withRefTracking(false).requireClassRegistration(true).withNumberCompressed(true).build(); furyX.register(SkillFire_S2C_Msg.class); furyX.register(SkillCategory.class); furyX.register(HarmDTO.class); furyArray = fury.serializeJavaObject(skillFire_s2C_msg); furyArrayX = furyX.serializeJavaObject(skillFire_s2C_msg); protoArray = SerializingUtil.serialize(skillFire_s2C_msg); jsonArray = JSONB.toBytes(skillFire_s2C_msg); jsonArrayWithBeanToArray =JSONB.toBytes(skillFire_s2C_msg, JSONWriter.Feature.BeanToArray); // String size = RamUsageEstimator.humanReadableUnits(RamUsageEstimator.sizeOf(skillFire_s2C_msg)); logger.debug("size:{}", RamUsageEstimator.sizeOf(skillFire_s2C_msg)); } @Benchmark public SkillFire_S2C_Msg furyDeserialize() { return fury.deserializeJavaObject(furyArray, SkillFire_S2C_Msg.class); } @Benchmark public SkillFire_S2C_Msg jsonDeserialize() { return JSONB.parseObject(jsonArray, SkillFire_S2C_Msg.class); } @Benchmark public SkillFire_S2C_Msg jsonDeserializeWithArrayToBean() { return JSONB.parseObject(jsonArrayWithBeanToArray, SkillFire_S2C_Msg.class, JSONReader.Feature.SupportArrayToBean); } @Benchmark public SkillFire_S2C_Msg jsonDeserializeWithArrayToBeanAndFieldBase() { return JSONB.parseObject(jsonArrayWithBeanToArray, SkillFire_S2C_Msg.class, JSONReader.Feature.SupportArrayToBean,JSONReader.Feature.FieldBased); } @Benchmark public SkillFire_S2C_Msg furyDeserializeWithClassRegistrationAndNumberCompressed() { return furyX.deserializeJavaObject(furyArrayX, SkillFire_S2C_Msg.class); } @Benchmark public SkillFire_S2C_Msg protostuffDeserialize() { return SerializingUtil.deserialize(protoArray, SkillFire_S2C_Msg.class); } public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() .include(ProtoDeserializeBenchMark.class.getSimpleName()).result("result.json") .resultFormat(ResultFormatType.JSON) // .addProfiler(GCProfiler.class) // .addProfiler(CompilerProfiler.class) // .verbosity(VerboseMode.EXTRA) .build(); new Runner(opt).run(); } } ================================================ FILE: gamioo-sandbox/src/jmh/java/io/gamioo/sandbox/ProtoSerializeBenchMark.java ================================================ package io.gamioo.sandbox; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONB; import com.alibaba.fastjson2.JSONReader; import com.alibaba.fastjson2.JSONWriter; import com.carrotsearch.sizeof.RamUsageEstimator; import com.github.houbb.data.factory.core.util.DataUtil; import io.fury.Fury; import io.fury.Language; import io.gamioo.common.util.FileUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.profile.*; import org.openjdk.jmh.results.format.ResultFormatType; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import org.openjdk.jmh.runner.options.VerboseMode; import java.util.concurrent.TimeUnit; /** * @author Allen Jiang */ @Fork(1) @Warmup(iterations = 10, time = 1) @Measurement(iterations = 10, time = 1) @OutputTimeUnit(TimeUnit.SECONDS) @BenchmarkMode({Mode.Throughput}) @State(Scope.Benchmark) public class ProtoSerializeBenchMark { private static final Logger logger = LogManager.getLogger(ProtoSerializeBenchMark.class); private SkillFire_S2C_Msg skillFire_s2C_msg = DataUtil.build(SkillFire_S2C_Msg.class); private Fury fury; private Fury furyX; @Setup public void init() { try { byte[] array = FileUtils.getByteArrayFromFile("message.txt"); skillFire_s2C_msg= JSON.parseObject(array,SkillFire_S2C_Msg.class); logger.info(skillFire_s2C_msg); } catch (Exception e) { logger.error(e.getMessage(), e); } fury = Fury.builder().withLanguage(Language.JAVA) .withRefTracking(true).requireClassRegistration(false).build(); furyX = Fury.builder().withLanguage(Language.JAVA) .withRefTracking(false).requireClassRegistration(true).withNumberCompressed(true).build(); furyX.register(SkillFire_S2C_Msg.class); furyX.register(SkillCategory.class); furyX.register(HarmDTO.class); // String size = RamUsageEstimator.humanReadableUnits(RamUsageEstimator.sizeOf(skillFire_s2C_msg)); logger.debug("size:{}", RamUsageEstimator.sizeOf(skillFire_s2C_msg)); } @Benchmark public byte[] furySerialize() { return fury.serialize(skillFire_s2C_msg); } @Benchmark public byte[] furySerializeWithClassRegistrationAndNumberCompressed() { return furyX.serialize(skillFire_s2C_msg); } @Benchmark public byte[] jsonSerialize() { return JSONB.toBytes(skillFire_s2C_msg); } @Benchmark public byte[] jsonSerializeWithBeanToArray() { return JSONB.toBytes(skillFire_s2C_msg, JSONWriter.Feature.BeanToArray); } @Benchmark public byte[] jsonSerializeWithBeanToArrayAndFieldBase() { return JSONB.toBytes(skillFire_s2C_msg, JSONWriter.Feature.BeanToArray,JSONWriter.Feature.FieldBased); } @Benchmark public byte[] protostuffSerialize() { return SerializingUtil.serialize(skillFire_s2C_msg); } public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() .include(ProtoSerializeBenchMark.class.getSimpleName()).result("result.json") .resultFormat(ResultFormatType.JSON) // .addProfiler(GCProfiler.class) // .addProfiler(CompilerProfiler.class) // .verbosity(VerboseMode.EXTRA) // .addProfiler(JavaFlightRecorderProfiler.class) .build(); new Runner(opt).run(); } } ================================================ FILE: gamioo-sandbox/src/jmh/java/io/gamioo/sandbox/SymmetricBenchmark.java ================================================ package io.gamioo.sandbox; import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; /** * JMH测试15:Asymmetric Benchmark样例 * * @author Allen Jiang */ @BenchmarkMode(Mode.AverageTime) @Fork(1) @Warmup(iterations = 5, time = 1) @Measurement(iterations = 5, time = 1) @OutputTimeUnit(TimeUnit.MICROSECONDS) @State(Scope.Group) public class SymmetricBenchmark { private AtomicLong counter; @Setup public void init() { this.counter = new AtomicLong(); } @GroupThreads(5) @Group("atomic") @Benchmark public void inc() { this.counter.incrementAndGet(); } @GroupThreads(5) @Group("atomic") @Benchmark public long get() { return this.counter.get(); } public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() .include(SymmetricBenchmark.class.getSimpleName()) .build(); new Runner(opt).run(); } } ================================================ FILE: gamioo-sandbox/src/jmh/resources/message.txt ================================================ {"attackerId":2013850838,"harmList":[{"curHp":1061639.1,"dead":true,"maxHp":972081.06,"real":36249,"targetId":1711281434,"type":84,"value":18168.72},{"curHp":836323.44,"dead":true,"maxHp":8546706.0,"real":91675,"targetId":1527336063,"type":22,"value":30714.76},{"curHp":2022717.6,"dead":true,"maxHp":8923567.0,"real":74008,"targetId":1684460215,"type":67,"value":93250.83}],"index":37,"param1":[7153337,1918282,5243103,1985757,7515730],"skillCategory":"ATTACK_PASSIVE"} ================================================ FILE: gamioo-sandbox/src/main/java/io/gamioo/sandbox/HarmDTO.java ================================================ package io.gamioo.sandbox; import com.github.houbb.data.factory.api.annotation.DataFactory; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; /** * @author Allen Jiang */ public class HarmDTO { @DataFactory(min = 1000000000, max = Integer.MAX_VALUE) private Long targetId;//受伤者 @DataFactory(min = 1, max = 100) private int type;// 伤害类型 @DataFactory(min = 0, max = 99999) private float value;//伤害值 private boolean dead; //是否致死 @DataFactory(min = 0, max = 99999) private long real; //真实伤害 @DataFactory(min = 0, max = 9999999) private float maxHp;//最大血量 @DataFactory(min = 0, max = 9999999) private float curHp;//当前血量 public Long getTargetId() { return targetId; } public void setTargetId(Long targetId) { this.targetId = targetId; } public int getType() { return type; } public void setType(int type) { this.type = type; } public float getValue() { return value; } public void setValue(float value) { this.value = value; } public boolean isDead() { return dead; } public void setDead(boolean dead) { this.dead = dead; } public long getReal() { return real; } public void setReal(long real) { this.real = real; } public float getMaxHp() { return maxHp; } public void setMaxHp(float maxHp) { this.maxHp = maxHp; } public float getCurHp() { return curHp; } public void setCurHp(float curHp) { this.curHp = curHp; } @Override public String toString() { return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); } } ================================================ FILE: gamioo-sandbox/src/main/java/io/gamioo/sandbox/LoginGame_S2C_Msg.java ================================================ package io.gamioo.sandbox; /** * @author Allen Jiang */ public class LoginGame_S2C_Msg { } ================================================ FILE: gamioo-sandbox/src/main/java/io/gamioo/sandbox/SerializingUtil.java ================================================ package io.gamioo.sandbox; import io.protostuff.LinkedBuffer; import io.protostuff.ProtostuffIOUtil; import io.protostuff.runtime.RuntimeSchema; /** * 序列化和反序列化工具类 * @author Allen Jiang */ public class SerializingUtil { /** * 将目标类序列化为byte数组 * * @param source * @param * @return */ public static byte[] serialize(T source) { RuntimeSchema schema; LinkedBuffer buffer = null; byte[] result; try { schema = RuntimeSchema.createFrom((Class) source.getClass()); buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE); result = ProtostuffIOUtil.toByteArray(source, schema, buffer); } catch (Exception e) { throw new RuntimeException("serialize exception"); } finally { if (buffer != null) { buffer.clear(); } } return result; } /** * 将byte数组反序列化为目标类 * * @param source * @param typeClass * @param * @return */ public static T deserialize(byte[] source, Class typeClass) { RuntimeSchema schema; T newInstance; try { schema = RuntimeSchema.createFrom(typeClass); newInstance = typeClass.newInstance(); ProtostuffIOUtil.mergeFrom(source, newInstance, schema); } catch (Exception e) { throw new RuntimeException("deserialize exception"); } return newInstance; } } ================================================ FILE: gamioo-sandbox/src/main/java/io/gamioo/sandbox/SkillCategory.java ================================================ package io.gamioo.sandbox; /** * @author Allen Jiang */ public enum SkillCategory { ATTACKED_PASSIVE(1," 受创时被动触发技能"), ACTIVE(2,"主动"), ATTACK_PASSIVE(3,"攻击时被动触发技能"), ATTRIBUTE_ATTRIBUTE(4,"属性技能"); private final int id; private final String message; SkillCategory(int id, String message) { this.id = id; this.message = message; } public int getId() { return id; } public String getMessage() { return message; } } ================================================ FILE: gamioo-sandbox/src/main/java/io/gamioo/sandbox/SkillFire_S2C_Msg.java ================================================ package io.gamioo.sandbox; import com.github.houbb.data.factory.api.annotation.DataFactory; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 技能回包 * @author Allen Jiang */ public class SkillFire_S2C_Msg { @DataFactory(min = 1000000000, max = Integer.MAX_VALUE) private Long attackerId; private SkillCategory skillCategory;//技能类型(大类) @DataFactory(min = 0, max = 100) private int index;//设置连招索引 @DataFactory(min = 0, max = 5) private List harmList=new ArrayList<>(); private List param1=new ArrayList<>(); // private Map store=new HashMap<>(); // public Map getStore() { // return store; // } // // public void setStore(Map store) { // this.store = store; // } public Long getAttackerId() { return attackerId; } public void setAttackerId(Long attackerId) { this.attackerId = attackerId; } public SkillCategory getSkillCategory() { return skillCategory; } public void setSkillCategory(SkillCategory skillCategory) { this.skillCategory = skillCategory; } public int getIndex() { return index; } public void setIndex(int index) { this.index = index; } public List getHarmList() { return harmList; } public void setHarmList(List harmList) { this.harmList = harmList; } public List getParam1() { return param1; } public void setParam1(List param1) { this.param1 = param1; } @Override public String toString() { return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); } } ================================================ FILE: gamioo-sandbox/src/test/java/io/gamioo/sandbox/AnsibleTest.java ================================================ package io.gamioo.sandbox; import com.github.woostju.ansible.AnsibleClient; import com.github.woostju.ansible.ReturnValue; import com.github.woostju.ansible.command.PingCommand; import com.github.woostju.ssh.SshClientConfig; import com.github.woostju.ssh.pool.SshClientsPool; import com.google.common.collect.Lists; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.junit.jupiter.api.*; import java.util.Map; @DisplayName("crypto test") @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class AnsibleTest { private static final Logger logger = LogManager.getLogger(AnsibleTest.class); private static SshClientsPool pool = new SshClientsPool(); private static AnsibleClient client; @BeforeAll public static void beforeAll() { client = new AnsibleClient(new SshClientConfig("115.159.87.108", 3737, "root", "", null), pool); // client = new AnsibleClient(); } @AfterAll public static void afterAll() { } @DisplayName("ssh") @Test @Order(1) public void ssh() { Map result = client.execute(new PingCommand(Lists.newArrayList("106.53.236.196")), 1000); logger.debug("result", result); } } ================================================ FILE: gamioo-sandbox/src/test/java/io/gamioo/sandbox/Base64Test.java ================================================ package io.gamioo.sandbox; import com.github.woostju.ansible.AnsibleClient; import com.github.woostju.ansible.ReturnValue; import com.github.woostju.ansible.command.PingCommand; import com.github.woostju.ssh.SshClientConfig; import com.github.woostju.ssh.pool.SshClientsPool; import com.google.common.collect.Lists; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.junit.Assert; import org.junit.jupiter.api.*; import java.util.Base64; import java.util.Map; @DisplayName("crypto test") @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class Base64Test { private static final Logger logger = LogManager.getLogger(Base64Test.class); @BeforeAll public static void beforeAll() { } @AfterAll public static void afterAll() { } @DisplayName("base64") @Test @Order(1) public void base64() { String ret= Base64.getEncoder().encodeToString("api_user:api_password".getBytes()); logger.debug("result:{}", ret); Assertions.assertEquals("YXBpX3VzZXI6YXBpX3Bhc3N3b3Jk", ret); } } ================================================ FILE: gamioo-sandbox/src/test/java/io/gamioo/sandbox/BitMapTest.java ================================================ package io.gamioo.sandbox; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.junit.jupiter.api.*; import java.io.IOException; import java.util.BitSet; @DisplayName("BitMap test") @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class BitMapTest { private static final Logger logger = LogManager.getLogger(BitMapTest.class); @BeforeAll public static void beforeAll() throws IOException { } @AfterAll public static void afterAll() { } @DisplayName("bitmap") @Test @Order(2) public void nativeFind() throws Exception { BitSet bit = new BitSet(); BitSet other = new BitSet(); logger.debug("size={}", bit.size()); int a[] = {2, 3, 14, 7, 0, 66}; //赋值 for (int num : a) { //进入某层 bit.set(num); } //离开某层 // bit.clear(2); int b[] = {1, 4}; for (int num : b) { other.set(num, true); } logger.debug("size={}", bit.size()); //排序 for (int i = 0; i < bit.size(); i++) { if (bit.get(i)) { logger.debug(i); } } if (bit.intersects(other)) { logger.debug("same layer"); } logger.debug("{}", bit.toByteArray()); logger.debug("{}", bit.toLongArray()); logger.debug("{}", bit.toString()); logger.debug("end"); } } ================================================ FILE: gamioo-sandbox/src/test/java/io/gamioo/sandbox/CryptoTest.java ================================================ package io.gamioo.sandbox; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.junit.jupiter.api.*; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import java.io.IOException; @DisplayName("crypto test") @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class CryptoTest { private static final Logger logger = LogManager.getLogger(CryptoTest.class); @BeforeAll public static void beforeAll() throws IOException { } @AfterAll public static void afterAll() { } @DisplayName("bCrypt") @Test @Order(1) public void bCrypt() { String password = "neil"; String encodedPwd = "$apr1$PjLB3DLO$J1zN2Bbit2A9e8FQdALjb0"; //密码加密 BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); //加密 String newPassword = passwordEncoder.encode(password); logger.debug("raw pwd:{},encoded pwd:{}", password, newPassword); //对比这两个密码是否是同一个密码 logger.debug("compare {},{}", password, encodedPwd); boolean matches = passwordEncoder.matches(password, encodedPwd); if (matches) { logger.debug("match"); } else { logger.debug("diff"); } } } ================================================ FILE: gamioo-sandbox/src/test/java/io/gamioo/sandbox/ProtoTest.java ================================================ package io.gamioo.sandbox; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONB; import com.alibaba.fastjson2.JSONReader; import com.alibaba.fastjson2.JSONWriter; import com.carrotsearch.sizeof.RamUsageEstimator; import com.github.houbb.data.factory.core.util.DataUtil; import com.github.houbb.heaven.util.lang.MathUtil; import io.fury.Fury; import io.fury.Language; import io.fury.ThreadLocalFury; import io.fury.resolver.MetaContext; import io.gamioo.common.util.FileUtils; import io.gamioo.common.util.MathUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.junit.jupiter.api.*; import java.io.IOException; @DisplayName("proto test") @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class ProtoTest { private static final Logger logger = LogManager.getLogger(ProtoTest.class); private static SkillFire_S2C_Msg skillFire_s2C_msg; private static Fury fury; private static byte[] bytes; @BeforeAll public static void beforeAll() throws IOException { try { byte[] array = FileUtils.getByteArrayFromFile("message.txt"); skillFire_s2C_msg= JSON.parseObject(array,SkillFire_S2C_Msg.class); logger.info(skillFire_s2C_msg); } catch (Exception e) { logger.error(e.getMessage(), e); } // skillFire_s2C_msg= DataUtil.build(SkillFire_S2C_Msg.class); // logger.info(JSON.toJSONString(skillFire_s2C_msg)); fury = Fury.builder().withLanguage(Language.JAVA) .withRefTracking(true).requireClassRegistration(false).withNumberCompressed(false).build(); long size = RamUsageEstimator.sizeOf(skillFire_s2C_msg); logger. info(size); } @AfterAll public static void afterAll() { } @DisplayName("Json2 Serializable") @Test @Order(1) public void handleJson2Serialize() { bytes = JSONB.toBytes(skillFire_s2C_msg); long size = RamUsageEstimator.sizeOf(skillFire_s2C_msg); // logger. info(bytes.length); // String size2 = RamUsageEstimator.humanReadableUnits(size); logger. info( "Json2 Serializable "+MathUtils.prettyPercentage((double)bytes.length/size)); } @DisplayName("Json2 Serializable with BeanToArray") @Test @Order(2) public void handleJson2WithBeanToArraySerialize() { bytes = JSONB.toBytes(skillFire_s2C_msg, JSONWriter.Feature.BeanToArray); long size = RamUsageEstimator.sizeOf(skillFire_s2C_msg); // logger. info(bytes.length); // String size2 = RamUsageEstimator.humanReadableUnits(size); logger. info( "Json2 Serializable with BeanToArray "+MathUtils.prettyPercentage((double)bytes.length/size)); } @DisplayName("Json2 Serializable with beanToArray and fieldBased") @Test @Order(2) public void handleJson2WithBeanToArrayAndFieldBasedSerialize() { bytes = JSONB.toBytes(skillFire_s2C_msg, JSONWriter.Feature.BeanToArray, JSONWriter.Feature.FieldBased); long size = RamUsageEstimator.sizeOf(skillFire_s2C_msg); // logger. info(bytes.length); // String size2 = RamUsageEstimator.humanReadableUnits(size); logger. info( "Json2 Serializable with beanToArray and fieldBased "+MathUtils.prettyPercentage((double)bytes.length/size)); } // @DisplayName("ThreadLocalFury Serializable") // @Test // @Order(3) // public void handleThreadLocalFurySerialize() { // ThreadLocalFury fury = Fury.builder().withLanguage(Language.JAVA) // .withRefTracking(true).requireClassRegistration(false).withNumberCompressed(false).buildThreadLocalFury(); // bytes = fury.serialize(skillFire_s2C_msg); // long size = RamUsageEstimator.sizeOf(skillFire_s2C_msg); // // logger. info(bytes.length); // // String size2 = RamUsageEstimator.humanReadableUnits(size); // logger. info( "Fury Serializable "+MathUtils.prettyPercentage((double)bytes.length/size)); // } // // @DisplayName("Fury Deserialize") // @Order(8) // public void handleThreadLocalFuryDeserialize() { // ThreadLocalFury fury2 = Fury.builder().withLanguage(Language.JAVA) // .withRefTracking(true).requireClassRegistration(false).withNumberCompressed(false).buildThreadLocalFury(); // // logger. info( fury.deserializeJavaObject(bytes,SkillFire_S2C_Msg.class)); // // logger. info( fury.deserializeJavaObject(bytes,SkillFire_S2C_Msg.class)); // } @DisplayName("Fury Serializable") @Test @Order(3) public void handleFurySerialize() { bytes = fury.serializeJavaObject(skillFire_s2C_msg); long size = RamUsageEstimator.sizeOf(skillFire_s2C_msg); // logger. info(bytes.length); // String size2 = RamUsageEstimator.humanReadableUnits(size); logger. info( "Fury Serializable "+MathUtils.prettyPercentage((double)bytes.length/size)); } @DisplayName("Fury Serializable with Number Compress") @Test @Order(4) public void handleFurySerializeWithCompress() { fury = Fury.builder().withLanguage(Language.JAVA) .withRefTracking(true).requireClassRegistration(false).withNumberCompressed(true).build(); bytes = fury.serializeJavaObject(skillFire_s2C_msg); long size = RamUsageEstimator.sizeOf(skillFire_s2C_msg); // logger. info(bytes.length); // String size2 = RamUsageEstimator.humanReadableUnits(size); logger. info("Fury Serializable with Number Compress "+MathUtils.prettyPercentage((double)bytes.length/size)); } @DisplayName("Fury Serializable with Number Compress and RefTracking close") @Test @Order(5) public void handleFurySerializeWithCompressAndRefTrackingClose() { fury = Fury.builder().withLanguage(Language.JAVA) .withRefTracking(false).requireClassRegistration(false).withNumberCompressed(true).build(); bytes = fury.serializeJavaObject(skillFire_s2C_msg); long size = RamUsageEstimator.sizeOf(skillFire_s2C_msg); // logger. info(bytes.length); // String size2 = RamUsageEstimator.humanReadableUnits(size); logger. info("Fury Serializable with Number Compress and RefTracking close "+ MathUtils.prettyPercentage((double)bytes.length/size)); } @DisplayName("Fury Serializable with Number Compress and RefTracking close and class register") @Test @Order(6) public void handleFurySerializeWithCompressAndRefTrackingCloseAndRegister() { fury = Fury.builder().withLanguage(Language.JAVA) .withRefTracking(false).requireClassRegistration(true).withNumberCompressed(true).build(); fury.register(SkillFire_S2C_Msg.class); fury.register(SkillCategory.class); fury.register(HarmDTO.class); bytes = fury.serializeJavaObject(skillFire_s2C_msg); long size = RamUsageEstimator.sizeOf(skillFire_s2C_msg); // logger. info(bytes.length); // String size2 = RamUsageEstimator.humanReadableUnits(size); logger. info("Fury Serializable with Number Compress and RefTracking close and class register "+MathUtils.prettyPercentage((double)bytes.length/size)); } @DisplayName("Fury Serializable with Number Compress and class register") @Test @Order(7) public void handleFurySerializeWithCompressAndRegister() { //.withDeserializeUnExistClassEnabled(true) fury = Fury.builder().withLanguage(Language.JAVA) .withRefTracking(true).requireClassRegistration(true).withNumberCompressed(true).build(); fury.register(SkillFire_S2C_Msg.class); fury.register(SkillCategory.class); fury.register(HarmDTO.class); bytes = fury.serializeJavaObject(skillFire_s2C_msg); long size = RamUsageEstimator.sizeOf(skillFire_s2C_msg); // logger. info(bytes.length); // String size2 = RamUsageEstimator.humanReadableUnits(size); logger. info("Fury Serializable with Number Compress and class register "+MathUtils.prettyPercentage((double)bytes.length/size)); } @DisplayName("Fury Deserialize") @Test @Order(8) public void handleFuryDeserialize() { // logger. info( fury.deserializeJavaObject(bytes,SkillFire_S2C_Msg.class)); } @DisplayName("Protostuff Serializable") @Test @Order(100) public void handleProtostuffSerialize() { bytes = SerializingUtil.serialize(skillFire_s2C_msg); long size = RamUsageEstimator.sizeOf(skillFire_s2C_msg); // logger.info(bytes.length); logger.info("Protostuff Serializable "+MathUtils.prettyPercentage((double)bytes.length/size)); } @DisplayName("Protostuff Deserialize") @Test @Disabled @Order(101) public void handleProtostuffDeserialize() { logger. info(SerializingUtil.deserialize(bytes, SkillFire_S2C_Msg.class)); } } ================================================ FILE: gamioo-sandbox/src/test/resources/message.txt ================================================ {"attackerId":2013850838,"harmList":[{"curHp":1061639.1,"dead":true,"maxHp":972081.06,"real":36249,"targetId":1711281434,"type":84,"value":18168.72},{"curHp":836323.44,"dead":true,"maxHp":8546706.0,"real":91675,"targetId":1527336063,"type":22,"value":30714.76},{"curHp":2022717.6,"dead":true,"maxHp":8923567.0,"real":74008,"targetId":1684460215,"type":67,"value":93250.83}],"index":37,"param1":[7153337,1918282,5243103,1985757,7515730],"skillCategory":"ATTACK_PASSIVE"} ================================================ FILE: gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists org.gradle.jvmargs=-Dfile.encoding=UTF-8 systemProp.org.gradle.internal.publish.checksums.insecure=true ================================================ FILE: gradlew ================================================ #!/usr/bin/env sh # # Copyright 2015 the original author or authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ############################################################################## ## ## 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='"-Xmx64m" "-Xms64m"' # 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 or MSYS, switch paths to Windows format before running java if [ "$cygwin" = "true" -o "$msys" = "true" ] ; 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=`expr $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 # Escape application args save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" exec "$JAVACMD" "$@" ================================================ FILE: gradlew.bat ================================================ @rem @rem Copyright 2015 the original author or authors. @rem @rem Licensed under the Apache License, Version 2.0 (the "License"); @rem you may not use this file except in compliance with the License. @rem You may obtain a copy of the License at @rem @rem https://www.apache.org/licenses/LICENSE-2.0 @rem @rem Unless required by applicable law or agreed to in writing, software @rem distributed under the License is distributed on an "AS IS" BASIS, @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem @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 Resolve any "." and ".." in APP_HOME to make it shorter. for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi @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="-Xmx64m" "-Xms64m" @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 ================================================ FILE: settings.gradle ================================================ rootProject.name = 'gamioo'; // 基础模块 include 'gamioo-common', 'gamioo-navigation', "gamioo-cache", "gamioo-compress", 'gamioo-config', "gamioo-protocol", "gamioo-network", "gamioo-event", "gamioo-orm", "gamioo-ioc", "gamioo-redis", "gamioo-log"; //测试沙箱模块 include 'gamioo-sandbox' // 游戏服务器 include "gamioo-game"; ================================================ FILE: version.properties ================================================ #version info #Fri Mar 10 20:15:12 CST 2023 release=18 date=20230310