Compare commits

..
Author SHA1 Message Date
LamGC 4fef222754 Merge pull request #46 from LamGC/dependabot/maven/ContentGrabbingJi-framework-api/com.google.code.gson-gson-2.8.9
Bump gson from 2.8.6 to 2.8.9 in /ContentGrabbingJi-framework-api
2022-06-06 23:31:38 +08:00
LamGC 3971143d9b Merge pull request #44 from LamGC/dependabot/maven/ContentGrabbingJi-pixiv/com.google.code.gson-gson-2.8.9
Bump gson from 2.8.6 to 2.8.9 in /ContentGrabbingJi-pixiv
2022-06-06 23:31:23 +08:00
dependabot[bot] f9ac67f7f8 Bump gson from 2.8.6 to 2.8.9 in /ContentGrabbingJi-framework-api
Bumps [gson](https://github.com/google/gson) from 2.8.6 to 2.8.9.
- [Release notes](https://github.com/google/gson/releases)
- [Changelog](https://github.com/google/gson/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/gson/compare/gson-parent-2.8.6...gson-parent-2.8.9)

---
updated-dependencies:
- dependency-name: com.google.code.gson:gson
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-05-31 10:04:55 +00:00
dependabot[bot] 6d91638413 Bump gson from 2.8.6 to 2.8.9 in /ContentGrabbingJi-pixiv
Bumps [gson](https://github.com/google/gson) from 2.8.6 to 2.8.9.
- [Release notes](https://github.com/google/gson/releases)
- [Changelog](https://github.com/google/gson/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/gson/compare/gson-parent-2.8.6...gson-parent-2.8.9)

---
updated-dependencies:
- dependency-name: com.google.code.gson:gson
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-05-31 10:04:48 +00:00
LamGC 66b2bbd3fd Merge pull request #45 from LamGC/dependabot/maven/ContentGrabbingJi-core/com.google.code.gson-gson-2.8.9
Bump gson from 2.8.6 to 2.8.9 in /ContentGrabbingJi-core
2022-05-31 18:04:30 +08:00
LamGC 2c3817172c Merge pull request #47 from LamGC/dependabot/maven/ContentGrabbingJi-CacheStore-redis/com.google.code.gson-gson-2.8.9
Bump gson from 2.8.6 to 2.8.9 in /ContentGrabbingJi-CacheStore-redis
2022-05-31 18:03:54 +08:00
dependabot[bot] 2764551e3b Bump gson from 2.8.6 to 2.8.9 in /ContentGrabbingJi-CacheStore-redis
Bumps [gson](https://github.com/google/gson) from 2.8.6 to 2.8.9.
- [Release notes](https://github.com/google/gson/releases)
- [Changelog](https://github.com/google/gson/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/gson/compare/gson-parent-2.8.6...gson-parent-2.8.9)

---
updated-dependencies:
- dependency-name: com.google.code.gson:gson
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-05-20 22:00:19 +00:00
dependabot[bot] 22eb78dae9 Bump gson from 2.8.6 to 2.8.9 in /ContentGrabbingJi-core
Bumps [gson](https://github.com/google/gson) from 2.8.6 to 2.8.9.
- [Release notes](https://github.com/google/gson/releases)
- [Changelog](https://github.com/google/gson/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/gson/compare/gson-parent-2.8.6...gson-parent-2.8.9)

---
updated-dependencies:
- dependency-name: com.google.code.gson:gson
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-05-20 21:57:03 +00:00
LamGC 7fd5c5bfc5 [Fix][Update] Pixiv 更新 HttpClient 依赖项的版本以修复该依赖项的一个漏洞;
[Update] org.apache.httpcomponents:httpclient 更新版本以修复 CVE-2020-13956 漏洞(4.5.12 -> 4.5.13);
2021-06-20 14:33:53 +08:00
LamGC 544c56281a [Fix][Update] Framework-TelegramBots 更新 HttpClient 依赖项的版本以修复该依赖项的一个漏洞;
[Update] org.apache.httpcomponents:httpclient 更新版本以修复 CVE-2020-13956 漏洞(4.5.12 -> 4.5.13);
2021-06-20 14:29:23 +08:00
LamGC e47a745a87 [Add] Project, Template-Velocity, Core-Function 添加新的模块;
[Add] Template-Velocity 尝试将 Velocity 作为官方支持的模板组件;
[Add] Core-Function 将 Function 相关功能从 Core 中抽离, 以尝试降低耦合性;
2021-04-09 12:56:54 +08:00
LamGC ed46aa9481 [Update] Project 更新 Guava 版本, 以避免临时文件访问权限漏洞;
[Update] com.google.guava:guava 更新版本(29.0-jre -> 30.0-jre) 以修复临时文件访问权漏洞;
2021-04-09 12:50:43 +08:00
LamGC 4492287632 [Document] Framework-API 补充 content 文档;
[Document] AbstractMessageEvent 补充 content 文档;
2021-03-14 19:36:28 +08:00
LamGC b7f9343ca9 [Change] Project 暂时停用 Codecov 测试覆盖率报告插件;
[Change] .github/workflows/maven-test.yml 停用 Codecov 插件;

------
Description:
Codecov 还有一些问题需要解决, 就目前项目情况来说暂时还没有过高的代码覆盖测试要求, 故暂时停用 Codecov.
2021-03-12 16:50:45 +08:00
LamGC dc86f99d60 [Move] Framework-API, Common 将 Platform 与 MessageSource 移入公共模块;
[Move] Platform 移入公共组件模块;
[Move] MessageSource 移入公共组件模块;
2021-03-12 16:45:01 +08:00
LamGC 30b6392ce2 [Add] Framework-API 为 NotFoundSenderException 异常添加新的构造器(早知道就一步到位了 :O);
[Add] NotFoundSenderException 添加新的构造器;
2021-03-01 09:14:13 +08:00
LamGC e494412344 [Change] Framework-API 添加 Bot UUID 作为在多账户框架组件中识别单个 Bot 账户的标识;
[Change] SenderFactory 添加 'getBotUUID' 方法用于获取 Factory 相关联的 Bot 账户;
2021-03-01 09:12:09 +08:00
LamGC 22ec7c6741 [Add] Framework-API 增加内置标准化 Platform Enum, 添加标准化平台信息文档;
[Add] Platform.Standard 添加私有内置的标准化 Platform 枚举类(已添加部分平台信息);
[Add] Framework-API/StandardPlatform.md 添加标准化平台信息文档(用于参考或者直接使用, 帮助统一平台信息);
2021-03-01 09:08:48 +08:00
LamGC c39c239d2d [Rename] Function-API(Command-API), Function-Pixiv(Command-Pixiv) 重命名模块;
[Rename] Command-API -> Function-API;
[Rename] Command-Pixiv -> Function-Pixiv;
[Change] Project/pom.xml 适配更改;
[Change] Core/pom.xml 适配更改;
2021-03-01 09:05:07 +08:00
LamGC 756df33442 [Change] Framework-API 调整 NoFoundSenderException 类名, 并添加可提供消息的构造器;
[Change] NoFoundSenderException -> NotFoundSenderException 更改类名;
[Change] NotFoundSenderException 添加可提供消息的构造器;
[Change] SenderFactory 适配更改;
2021-02-21 20:40:16 +08:00
LamGC f945b52fa4 [Change] Framework-API 调整 Framework 内存储 PluginWrapper 的方式, 以防 Plugin 内的 wrapper 遭到更改;
[Change] Framework 添加 _wrapper 字段, 用于保存 PluginWrapper 并防止 Plugin 内的 wrapper 遭到更改(但是依然没办法防止 'getWrapper()' 返回错误的 PluginWrapper);
2021-02-21 20:31:47 +08:00
LamGC 68f13a38c1 [Add] Framework-API 添加提供给应用内部(或命令扩展)用于主动获取对应平台 Sender 的扩展接口;
[Add] SenderFactory 添加用于主动获取 Sender 的扩展接口;
[Add] NoFoundSenderException 添加相关异常;
2021-02-21 20:29:33 +08:00
LamGC 711c80175e [Move] Core, Event-API(Event), Framework-API, Template-API(Template) 更改模块名以规范命名;
[Move] :Event-API 更改模块名(原 'Event');
[Move] :Template-API 更改模块名(原 'Template');
[Change] Project/pom.xml 适配更改;
[Change] :Core, :Framework-API 适配更改;
2021-02-13 13:09:16 +08:00
LamGC 612956b594 [Delete] Core, Framework-API 移除 BotCode 的 'getPlatform' 方法;
[Delete] BotCode 移除目前尚未使用的 'getPlatform' 方法, 该方法在开发 BotCode 具体实现时带来了一些麻烦;
[Delete] BasicBotCode, BasicBotCodeTest, AbstractBotCode, AbstractBotCodeTest, MessageChainTest 适配修改;
2021-02-03 12:13:32 +08:00
LamGC 672be33866 [Optimize] Core 修正一个代码上的细节, 以减少强转隐患;
[Optimize] FrameworkFactory 调整 'constructor' 的类型, 以减少强转类型错误的隐患;
2021-01-25 19:34:14 +08:00
LamGC f0053f3957 [Change] Project 修复贡献准则的细节错误, 调整联系邮箱的形式;
[Change] CODE_OF_CONDUCT.md 修复语法错误, 调整联系邮箱的形式;
2021-01-18 12:37:57 +08:00
LamGC f414802c63 [Add] CacheStore-Redis 添加指定前缀键名删除脚本, 以提供带有原子性的 RedisCacheStore.clear 操作;
[Add] RemoveKeysByPrefix.lua 添加用于实现 RedisCacheStore.clear 操作的脚本;
2021-01-16 01:43:00 +08:00
LamGC c956c1b3d2 [Change] Common 补充一个漏网之鱼;
[Change] LocalFactory 适配更改(Commit: a090abc9);
2021-01-16 01:32:41 +08:00
LamGC 92175377c4 [Add][Change][Fix] CacheStore-Redis 完善整体单元测试项, 修复单元测试不够严谨的问题;
[Add] RedisTestUtils 增加 Redis 测试工具类;
[Add] RedisUtilsTest 增加针对 RedisUtils 的单元测试类;
[Add] SimpleRedisCacheStore, RedisCacheStoreTest 将 RedisCacheStore 的测试项从 RedisSingleCacheStore 单元测试中分离;
[Change] RedisCacheStore 分离 'getKeyString(CacheKey)' 方法的具体实现到 RedisUtils;
[Change] RedisSingleCacheStore 调整构造器参数检查过程, 排除空标识或 null 参数;
[Change] RedisSingleCacheStoreTest 重写 RedisSingleCacheStore 单元测试类, 使其更加严谨;
[Change] RedisMapCacheStore 优化 'clearMap()' 方法执行过程, 清除不可能分支, 调整构造器参数检查过程, 排除空标识或 null 参数;
[Change] RedisMapCacheStoreTest 优化测试项, 使其更加严谨;
[Change] RedisListCacheStore 优化文档内容, 清除不可能分支;
[Change] RedisListCacheStoreTest 补充断言消息;
[Fix] RedisCacheStore 修复了 'keySet()' 方法中返回的 key 不正确的问题;
2021-01-16 01:26:31 +08:00
LamGC 997b942a27 [Change] CacheStore-Redis 调整通配符常量的引用;
[Change] RedisUtils 调整 'CACHE_KEY_ALL' 的通配符为 'KEY_PATTERN_ALL';
2021-01-15 20:20:02 +08:00
LamGC 48404fc0fc [Change] CacheStore-Redis 使用 Json 代替 Properties 作为 Redis 连接配置的存储格式;
[Add] RedisConnectionProperties 添加 Redis 连接配置对象;
[Change] RedisConnectionPool 调整 JedisPool 配置读取来源的类型(URL -> RedisConnectionProperties);
[Change] RedisCacheStoreFactory 更改 Redis 连接配置的读取形式;
[Add] RedisUtils 添加 'PROPERTIES_FILE_NAME' 常量;
[Add] com.google.code.gson:gson 添加 Gson 依赖项, 以转换 Json 为 RedisConnectionProperties 对象;
2021-01-15 20:18:20 +08:00
LamGC a090abc9a4 [Change] CacheStore-API, CacheStore-Local, CacheStore-Redis 调整 MapCacheStore.putAll 方法参数;
[Change] MapCacheStore 调整 'putAll' 方法的泛型, 以兼容类型的子类;
[Change] HashMapCacheStore, RedisMapCacheStore 适配更改;
2021-01-12 06:39:48 +08:00
LamGC edb709fb0d [Update] CacheStore-Redis 在构建配置中将 lua 脚本纳入编译资源;
[Update] CacheStore-Redis/pom.xml 将 lua 源码文件夹纳入资源一部分;
2021-01-11 16:45:30 +08:00
LamGC 8108379f96 [Change] CacheStore-Redis 迁移工具方法到 RedisUtils;
[Change] RedisCacheStore, RedisUtils 迁移 'getKeyString(CacheKey)' 方法的具体实现到 RedisUtils (方法: 'toRedisCacheKey(String, CacheKey)');
[Add] RedisUtils 添加 CACHE_KEY_ALL 以构建对 CacheStore 所有 Key 的匹配规则;
2021-01-11 16:44:22 +08:00
LamGC a6fc04c07e [Fix] CacheStore-Redis 修复 RedisCacheStore 部分操作对于作用 Key 的范围不明确的问题;
[Fix] RedisCacheStore 修复 'clear()', 'keySet()' 和 'size()' 方法影响到不属于 CacheStore 的问题;
[Update] RedisSingleCacheStoreTest 完善测试项;
2021-01-11 16:42:06 +08:00
LamGC 2bdfbeb72d [Fix] CacheStore-Redis 修复 'putAll' 因 Map 为空且检查疏漏导致抛出异常的问题, 完善测试项;
[Fix] RedisMapCacheStore 修复 'putAll' 因 Map 为空且检查疏漏导致抛出异常的问题;
[Update] RedisMapCacheStoreTest 调整测试用前缀, 完善 'putAll' 对空 Map 参数在不同情况下的测试;
2021-01-11 16:38:59 +08:00
LamGC 8d9debeb1b [Add] CacheStore-Redis 添加 List 组件;
[Add] RedisListCacheStore 添加完整的 List 组件;
[Add] RedisListCacheStoreTest 完整单元测试类;
[Update] RedisCacheStoreFactory 添加对 List 组件的提供;
2021-01-11 16:38:30 +08:00
LamGC 752cf907d6 [Add] RedisConnectionPool 添加对 Lua 脚本的支持;
[Add] LuaScript 添加 Lua 脚本声明定义类;
[Add] RedisConnectionPool 添加对 Lua 脚本加载管理和执行的功能;
2021-01-11 16:37:50 +08:00
LamGC f1e76092a0 [Fix] CacheStore-Redis 修复 RemoveElementByIndex 脚本中对无效项判断的错误;
[Fix] RemoveElementByIndex.lua 修复范围溢出, 不存在键检查失效的问题;
2021-01-11 16:24:55 +08:00
LamGC e270c6cb1d [Add] Project, Framework-OneBot 添加 OneBot 框架组件子模块;
[Add] Project/pom.xml 添加子模块;
[Add] Framework-OneBot/pom.xml 添加子模块;
[Add] Framework-OneBot/Readme.md 添加模块说明;
2021-01-09 16:39:59 +08:00
LamGC 6ad1d3a947 [Change] Project 调整用词;
[Change] README.md 调整用词;
2021-01-03 11:47:43 +08:00
LamGC 074e71014a [Fix] CacheStore-Redis 修复 RemoveElementByIndex 脚本返回值不统一的问题;
[Fix] RemoveElementByIndex.lua 修复失败返回值不统一的问题;
2021-01-03 11:28:52 +08:00
LamGC 1c298c11ec [Fix] Project 修复 Readme 中不符合规范的格式, 修正开发计划格式;
[Fix] README.md 修复 Readme 中不符合规范的格式, 修正开发计划格式;
2021-01-02 23:03:33 +08:00
LamGC 72ff066f5c [Add] CacheStore-Redis 添加两个 Lua 操作脚本;
[Add] CheckElementContains.lua 添加用于检查 List 中是否含有某元素的脚本;
[Add] RemoveElementByIndex.lua 添加用于通过指定索引删除元素的脚本;
2021-01-02 22:50:34 +08:00
LamGC b9141f9f96 [Update] Project 补充 Readme 中对本项目版权(开源协议)的描述, 调整 AGPLv3 协议声明的相关细节(补充 URL);
[Update] README.md 补充 Readme 中对本项目版权(开源协议)的描述, 调整 AGPLv3 协议声明的相关细节(补充 URL);
2021-01-02 17:53:15 +08:00
LamGC 37689f7253 [Update][Fix] CacheStore-Redis 增加测试细节, 修复 RedisMap 中 'putAll()' 方法非空检查失败导致发生异常的问题;
[Update] RedisMapCacheStoreTest 增加对 'putAll()' 参数传递空 Map 的测试;
[Fix] RedisMapCacheStore 修复因条件错误导致未能准确排除空 Map 的问题;
2021-01-02 12:30:56 +08:00
LamGC 75458dd999 [Fix] Project 修复 Readme 中的版权声明;
[Fix] README.md 修复版权声明(AGPLv3-or-later -> AGPLv3-Only);
2021-01-01 18:51:11 +08:00
LamGC 0968c5c6af [Update][Fix] ALL 修正版权信息, 更新年份;
[Update] * 更新年份;
[Fix] * 由 AGPLv3-Later 更新为 AGPLv3-Only;

2021, 新年快乐! (新的一年继续努力吧! :D)
2021-01-01 18:26:20 +08:00
LamGC de3c3941f3 [Add] Event 添加 Readme 文档;
[Add] Readme.md 添加子模块 Readme 文档, 待完善;
2021-01-01 17:51:13 +08:00
LamGC 87e6dbb9e7 [Add] CacheStore-Local 添加单元测试以完善模块单元测试覆盖;
[Add] LocalCacheStoreFactoryTest 添加针对 LocalCacheStoreFactory 的单元测试类;
[Add] HashSetCacheStoreTest 添加针对 HashSetCacheStore 的单元测试类;
2021-01-01 12:30:06 +08:00
LamGC 18107db418 [Optimize] CacheStore-Local 修复代码规范错误;
[Optimize] CopyOnWriteArrayListCacheStore 整理格式;
2021-01-01 11:49:47 +08:00
LamGC 280c5b22ac [Rename] CacheStore-Local 更改测试类以符合规范(?);
[Rename] ListCacheStoreTest -> CopyOnWriteArrayListCacheStoreTest;
2021-01-01 11:47:14 +08:00
LamGC 33c2cbf1ce [Change] Framework-API 调整 MessageSender, 移除 'getMessageIdentify()' 方法, 调整图片相关方法;
[Change] MessageSender 移除 'getMessageIdentify()' 方法, 更改 'getImageUrl(String)' 方法的返回值(String -> URL), 为 'getImageAsInputStream' 添加默认方法;
2021-01-01 10:18:14 +08:00
LamGC f1e248a702 [Change][Document] Core 修正 ThreadPoolEventExecutor 文档错误, 添加对 Cancelable 事件的操作细节;
[Change] ThreadPoolEventExecutor 适配 HandlerRegistry 更改;
[Change] ThreadPoolEventExecutor 对内部类 'ExecuteRunnable' 添加对 Cancelable 事件的更多处理;
[Document] ThreadPoolEventExecutor 修正文档错误;
2021-01-01 10:16:09 +08:00
LamGC 82759225b3 [Change][Document] Framework-API 调整 Framework 的部分文档内容, 更改相关细节;
[Document] Framework 修正 '<init>(PluginWrapper, File, FrameworkContext)' 和 'getDataFolder()' 方法的文档细节错误;
[Change] Framework 更改 'getDataFolder()' 方法在数据目录创建失败时所输出的日志内容;
[Change] Framework 更改 'getDescriptor()' 所抛出的异常类型;
2021-01-01 10:13:34 +08:00
LamGC 10b36e5f80 [Change] Core 为 FrameworkManager 添加对 'validatePluginDescriptor' 的具体实现, 以对 FrameworkDescriptor 进行检查;
[Change] FrameworkManager 覆盖 'validatePluginDescriptor' 方法, 补充对 FrameworkDescriptor 的检查;
2021-01-01 10:07:32 +08:00
LamGC a5f85227f2 [Change] Core, Event 调整文档和访问权, 添加默认值;
[Change] DefaultFrameworkContext 调整访问权;
[Change] DefaultFrameworkDescriptor 添加忽略的编译警告, 为 'version' 字段添加默认值;
[Change] EventHandler 调整注解文档;
2021-01-01 10:05:53 +08:00
LamGC 5246df6b50 [Change] CacheStore-Local 删除不可能条件;
[Change] HashCacheStore 删除不可能条件(Hashtable 在 put 中已经进行了 Null 检查);
2021-01-01 10:03:26 +08:00
LamGC 70cf231842 [Change][Document] Event 补充文档并添加默认方法;
[Document] Cancelable 补充文档;
[Change] Cancelable 为 'observableCancel()' 和 'registerCancelObserver(Observer)' 添加默认方法;
2021-01-01 09:58:40 +08:00
LamGC 8b2f882070 [Delete] Framework-API 正式删除 MessageSource 中的 'UNKNOWN' 消息源类型;
[Delete] MessageSource 删除 'UNKNOWN' 类型;
2021-01-01 09:56:45 +08:00
LamGC 8c12f17329 [Rename] Event, Core 调整类名;
[Rename] DefaultEventHandlerRegistry -> DefaultHandlerRegistry;
[Rename] EventHandlerRegistry -> HandlerRegistry;
2021-01-01 09:54:57 +08:00
LamGC 1d8148f4c7 [Add][Update] CacheStore-API 添加新的测试, 并完善其他测试;
[Add] GetCacheStoreExceptionTest 添加针对 GetCacheStoreExceptionTest 的测试项;
[Update] HashSingleCacheStoreTest 完善单元测试项;
2021-01-01 09:45:19 +08:00
LamGC 10fffca8b2 [Fix] CacheStore-Local 修复 AutoCleanTimer 因逻辑错误未能及时轮询清理 Cleanable 的问题;
[Fix] AutoCleanTimer 调整 'run()' 方法, 修复轮询执行错误并改善无效 Cleanable Reference 的清除方式;

Bug Description: 当发现 Reference 的指向为 null 时, 在收集了该对象后将会直接 return 导致后续轮询结束.
2021-01-01 09:29:51 +08:00
LamGC afd09968ac [Add] CacheStore-Local 为 AutoCleanTimer 添加单元测试;
[Add] AutoCleanTimer 添加针对 AutoCleanTimer 的单元测试类;
2021-01-01 09:29:23 +08:00
LamGC ed8e4701e8 [Update] Project 更新 Readme, 添加开发计划(暂定?);
[Update] README.md 添加开发计划说明;
2020-12-23 23:22:07 +08:00
LamGC 129ff21734 [Add] Project, Framework-Telegram, Template 添加模块;
[Add] Project/pom.xml 添加新的子模块;
[Add] Framework-Telegram 正式添加对 Telegram 的官方平台支持;
[Add] Template 将消息模板相关功能由 Core 分离为单独的模块;
2020-12-23 23:14:48 +08:00
LamGC 50761a6e4f [Update] Project 更新 .gitignore 文件, 添加对测试目录的忽略, 补充 logs 文件夹匹配规则;
[Update] .gitignore 添加 'test' 文件夹的匹配规则, 补充 'logs' 文件夹的匹配规则;
2020-12-23 23:11:50 +08:00
LamGC 98ca3a1e09 [Update] Project 更新 .gitignore 文件, 以补充某些文件夹的规则;
[Update] .gitignore 更改对 logs 文件夹的匹配规则, 以屏蔽子模块文件夹中的 logs 文件夹;
2020-12-20 22:32:56 +08:00
LamGC 4c384cc991 [Add][Update][Document] Framework-API, Project 更新Readme文档内容;
[Update] Project/README.md 更新一些贡献内容, 修正文档问题;
[Update] Framework-API/Readme.md 补充框架生命周期内容;
[Add] Framework-API/Quick-Start.md 添加尚未完成的快速开始教程文档, 计划在 3.0.0 正式版发布时完成;
2020-12-19 14:08:27 +08:00
LamGC 2ea0c08149 [Add] Framework-API 添加 BotCodeFunction 用于描述 BotCode;
[Add] BotCodeFunction 添加用于描述 BotCode 的功能描述接口;
[Add] StandardBotCodeFunction, CustomBotCodeFunction 添加一个受到 ContentGrabbingJi 内部支持的 Function Enum, 和一个可用于创建平台特有, 或补充功能用的 Function 自定义实现;
[Change] AbstractBotCode, AbstractBotCodeTest 适配修改;
[Change] BotCode 适配修改;
[Change] BasicBotCode, BasicBotCodeTest 适配修改;
[Change] MessageChainTest 适配修改;
2020-12-18 20:06:43 +08:00
LamGC 5ef90a1756 [Fix] Framework-API 修复 Framework 内多个自带方法可以被覆写的问题;
[Fix] Framework 对内置的, 非覆写方法添加 final 关键字以阻止覆写;
2020-11-28 18:01:59 +08:00
LamGC eaec7166b4 [Add][Document] Framework-API 为 Framework 提供获取包内资源的方法, 补充 'getContext()' 方法的文档;
[Add] Framework 添加 'getFrameworkResourceAsStream' 方法以获取 Jar 内资源;
[Document] Framework 补充 'getContext()' 方法的文档;
2020-11-28 11:31:31 +08:00
LamGC 7d0124418f [Change] Core 调整 Context 内部对象的传递方式;
[Add] CloneableFrameworkContext 添加可克隆 Context 抽象类;
[Change] DefaultFrameworkContext 更改实现为继承 CloneableFrameworkContext 以允许克隆 Context;
[Change] FrameworkManager, FrameworkFactory 更改 Context 内部对象的传递方式, 改用 CloneableFrameworkContext 进行传递;
2020-11-28 11:29:31 +08:00
LamGC 67510aa305 [Add] Framework-API 添加消息事件抽象类;
[Add] AbstractMessageEvent 初步添加一个消息事件类, 后续可能有变动;
2020-11-27 12:39:16 +08:00
LamGC 3e34b3605b [Add] Core 初步添加 Event 模块的具体实现和对 Message 的 Handler;
[Add] ThreadPoolEventExecutor, DefaultEventHandlerRegistry 添加基于线程池的事件执行器和事件处理注册器;
[Add] MessageEventHandler 添加一个尚未完成的 MessageEvent 处理类;
2020-11-26 23:48:27 +08:00
LamGC a9b693a0a5 [Change] Framework-API 在 MessageSender 中的 'uploadImage(File)' 增加对文件存在与否的检查;
[Change] MessageSender 增加文件存在与否的检查;
2020-11-26 23:44:50 +08:00
LamGC a4ff698716 [Change] Framework-API 为 MessageChain 实现 Iterable 接口;
[Change] MessageChain 实现 Iterable 接口用于更便捷的处理 Message;
2020-11-26 23:43:38 +08:00
LamGC 62a46d9348 [Add] Framework-API 为 BotCode 添加 'hasProperty(String)' 方法以用于检查某参数是否存在;
[Add] BotCode 添加 'hasProperty(String)' 方法以用于检查某参数是否存在;
[Change] AbstractBotCode 添加新方法的实现;
2020-11-26 23:41:40 +08:00
LamGC c7d0ccb3ef [Document] Framework-API 为 Framework 补充文档;
[Document] Framework 补充构造方法的参数文档;
2020-11-26 21:41:13 +08:00
LamGC 536e03cc32 [Document] Core 为 JarFrameworkLoader 补充文档内容;
[Document] JarFrameworkLoader 补充类文档内容;
2020-11-24 12:33:59 +08:00
LamGC 039a686f90 [Change] Core 为 FrameworkManager 内的 DevelopmentPluginLoader 补充对类加载策略的设置;
[Change] FrameworkManager 为 DevelopmentPluginLoader 重写 'createPluginClassLoader' 方法, 以提供 ClassLoadingStrategy 策略;
2020-11-22 20:19:27 +08:00
LamGC cd5c813b70 [Add] Project 添加对 Codecov 的配置文件, 禁用 Codecov 的 Github Checks 功能;
[Add] codecov.yaml 添加配置文件, 并禁用 Github Checks(目前处于未完成版, 尚不需要过于严格的覆盖率保证);
2020-11-22 12:07:12 +08:00
LamGC 7ee649296b [Add] Core 添加针对 JarFrameworkLoader 的完整单元测试;
[Add] JarFrameworkLoaderTest 添加完整单元测试类;
2020-11-22 11:12:54 +08:00
LamGC a5669e88e4 [Update][Dependency] Framework-API 更新 Pf4j 版本;
[Update] org.pf4j:pf4j 更新依赖项版本(3.4.1 -> 3.5.0);
2020-11-22 11:00:00 +08:00
LamGC b0041d7d9d [Fix][Add][Dependency] Framework-Mirai 修复框架描述文件的规范错误, 添加 package 打包插件;
[Fix] framework.json 修复描述文件对必须版本要求(requiresVersion)的规范错误;
[Add][Dependency] Framework-Mirai/pom.xml 添加 maven-assembly-plugin 插件以打包所需依赖项(模块是单独的, 并非与其他模块一起打包);
2020-11-22 10:58:45 +08:00
LamGC 82c97cc924 [Change] Core 调整 pf4j 在测试时的日志阀值, 以解决其话痨的问题;
[Change] log4j2-test.xml 调整 Pf4j 的日志最低阀值为 DEBUG;
2020-11-22 10:55:33 +08:00
LamGC a4340ab575 [Fix] Core, Framework-API 将 Framework 中 'initial()' 的调用转移到 FrameworkFactory 中, 以解决类初始化问题;
[Change] Framework 移除 'initial()' 的调用, 以修复类初始化过程中的一些隐式问题(如因父类尚未初始化完成, 导致子类成员变量尚未初始化, 出现 NPE 的问题);
[Change] FrameworkFactory 将 Framework 的初始化转移到此, 以修复类初始化过程中的一些隐式问题(如上);
2020-11-22 10:36:47 +08:00
LamGC 02bbab8e6d [Fix] Core 修复因 PluginClassLoader 默认类加载策略错误导致类重复加载;
[Add] JarFrameworkLoader 添加一个调整了类加载策略的 Loader 构造器;
[Fix] FrameworkManager 注册 JarFrameworkLoader 以解决类加载策略错误导致的类重复加载问题;
2020-11-22 10:32:49 +08:00
LamGC 3f353cbfa3 [Update][Dependency] Framework-Mirai 更新 mirai 版本号;
[Update][Dependency] net.mamoe:mirai-core, net.mamoe:mirai-core-qqandroid 更新依赖项版本(1.3.2 -> 1.3.3);
2020-11-21 22:03:31 +08:00
LamGC ba2ced9a1e [Add][Change] Framework-API 将 Message 正式代替 String 作为消息内容对象, 为 Framework 添加方便获取 Platform 对象的方法;
[Add] Framework 添加 `getPlatform` 方法以便捷得获取 Platform 对象;
[Change] MessageSender 使用 Message 代替 String 作为消息内容对象;
2020-11-21 22:01:57 +08:00
LamGC 561dd58611 [Optimize] CacheStore-API, Framework-API, Event, Common 补充多个单元测试类的文档注释;
[Optimize] CacheKeyTest, CollectionUtilsTest, EventUtilsTest, FactoryInfoTest 在类文档添加测试所针对的类;
2020-11-21 11:06:22 +08:00
LamGC 04153749e0 [Add] CacheStore-API 添加针对 StringToStringConverterTest 的单元测试类;
[Add] StringToStringConverterTest 添加完整单元测试类(最简单的测试类);
2020-11-21 10:44:18 +08:00
LamGC 736f861e01 [Update] Framework-API 在 MessageChainTest 补充测试项;
[Update] MessageChainTest 补充 'plusMessageTest' 测试项;
2020-11-21 10:28:11 +08:00
LamGC f173bde1a2 [Add] Framework-Mirai 添加 mirai 框架的 framework.json;
[Add] framework.json 添加框架描述文件;
2020-11-21 10:19:07 +08:00
LamGC dc1abdbdac [Add] Framework-API, Core 添加多个单元测试类;
[Add] AbstractBotCodeTest, BasicBotCodeTest, CollectionUtilsTest, MessageChainTest 添加多个针对相应类的完整单元测试类;
2020-11-21 10:14:55 +08:00
LamGC 12b8b41ac3 [Update] Framework-API 完善 MessageChain, 添加多个消息链操作方法;
[Update] MessageChain 添加多个操作方法, 补充参数检查;
2020-11-21 10:13:38 +08:00
LamGC 1bbaf1f164 [Fix] Core 修复 BasicBotCode 中 'contentToString()' 格式错误的问题;
[Fix] BasicBotCode 修复 'contentToString()' 尾部缺少方括号的问题;
2020-11-21 09:15:27 +08:00
LamGC 2520c79c50 [Change] Framework-API 调整 AbstractBotCode 中 'toString()' 的实现细节, 添加参数检查;
[Change] AbstractBotCode 调整 'toString()', 整理实现细节并修复格式错误, 在构造方法中添加对 functionName 的非空检查, 补充对 functionProperties 的非空判断条件, 补充文档;
2020-11-21 09:13:17 +08:00
LamGC 320858c4d0 [Change] Framework-API 为 CharSequenceMessage 构造中添加非空检查, 添加完整的对应单元测试类;
[Change] CharSequenceMessage 添加对 content 为 null 的检查, 调整文档;
[Change] CharSequenceMessageTest 添加针对 CharSequenceMessage 的完整单元测试类;
2020-11-20 23:16:14 +08:00
LamGC 7fb4f6d3df [Add] Framework-API 添加针对 Message 中默认实现方法的单元测试类;
[Add] MessageTest 添加针对默认实现方法的单元测试类;
2020-11-20 23:05:36 +08:00
LamGC 1fd1f2cf26 [Change] Framework-API 在 Message 中添加 CharSequence 的三个抽象方法的默认实现;
[Change] Message 添加 CharSequence 中 'length()', 'charAt(int)' 和 'subSequence(int, int)' 的默认实现, 以降低测试复杂度;
[Change] MessageChain, CharSequenceMessage, AbstractBotCode 适配 Message 更改;
2020-11-20 23:00:29 +08:00
LamGC 2c20ccb69f [Change] Core 补充 JsonFrameworkDescriptorSerializerTest 测试类的文档(就补了相关测试类);
[Change] JsonFrameworkDescriptorSerializerTest 在类文档中添加了测试相关类;
2020-11-20 22:30:57 +08:00
LamGC b21e6957a5 [Add] Framework-API 补充一个字符序列 Message 实现;
[Add] CharSequenceMessage 添加一个单调的实现(用于装 String, StringBuilder 或 StringBuffer 之类的);
2020-11-19 22:17:58 +08:00
LamGC b2fc196830 [Add][Change] Framework-API, Core 添加 Message 类型作为消息元素的基类, BotCode 继承 Message, AbstractBotCode 实现 BotCode 的相关方法细节;
[Add] Message, MessageChain 添加 Message 接口作为消息元素的基类, 添加 MessageChain 用于链接消息元素;
[Change] BotCode, AbstractBotCode BotCode 继承 Message 接口以作为消息的一部分, AbstractBotCode 实现部分方法细节;
[Add] BasicBotCode 适配调整, 添加新的构造方法;
2020-11-19 22:12:43 +08:00
LamGC 1853461f09 [Change] Framework-API 补充 Platform 的文档, 并添加 equals 和 hashCode 方法覆写;
[Change] Platform 为构造方法添加非空检查, 补充文档并添加 equals/hashCode 方法;
2020-11-19 22:09:33 +08:00
LamGC 76371b3257 [Fix] Common 修复潜在的并发问题, 修复测试用例不严谨的问题;
[Fix] CacheStoreBuilder 修复潜在的因并发锁错误导致的重载崩溃的问题;
[Fix] CacheStoreBuilderTest 调整测试用例, 以确保多线程重载获取的问题不会被捕获;
2020-11-19 22:01:37 +08:00
LamGC 89ef4e00c8 [Add] Core, Common 添加测试用日志配置文件;
[Add] Core/log4j2-test.xml 添加测试用配置(屏蔽 HttpClient debug以下级别);
[Add] Common/log4j2-test.xml 添加测试用配置(屏蔽 CacheStoreBuilder 所有日志);
2020-11-19 21:57:41 +08:00
LamGC 7ea90284e7 [Add] Core 添加一个内部使用(也可能会用到模版上)的 BotCode 实现;
[Add] BasicBotCode 添加内部实现的 BotCode;
2020-11-19 14:13:54 +08:00
LamGC e4617e6fee [Update] Project 添加测试覆盖率 Tag 到 Readme;
[Update] README.md 添加测试覆盖率 Tag;
2020-11-14 17:00:38 +08:00
LamGC bcded8c9f6 [Add][Change] Common 调整 FactoryInfo equals 判断细节, 添加对 FactoryInfo 的全覆盖单元测试;
[Change] FactoryInfo 调整 equals 方法, 移除优先级判断以防止因更新优先级导致出现同样组件有不同版本的情况;
[Add] FactoryInfoTest 添加单元测试项(100%);
[Add] CacheStoreSourceNoEqualFactory, FactoryInfoTestFactory, NameNoEqualFactoryA, NameNoEqualFactoryB, TooHighPriorityFactory, TooLowPriorityFactory, UnnamedFactory 添加测试用工厂(不要把它们注册到 SPI 中!);
2020-11-14 16:39:58 +08:00
LamGC 236f15825b [Update] Common 调整 CacheStoreBuilder 的 factory 重载过程, 增强测试覆盖率, 将 FactoryInfo 设计为不可继承;
[Change] FactoryInfo 添加 final 关键字以阻止继承;
[Change] CacheStoreBuilder 调整 factory 的重载过程, 使其尽可能符合线程安全(目前就测试结果而言应该是没问题的), 将 Factory 非空检查独立成一个方法, 以方便测试;
[Delete] Common/pom.xml 移除对 CacheStore-local 和 CacheStore-redis 的依赖;
[Change] CacheStoreBuilderTest 调整单元测试以增加测试覆盖率;
[Add] AvailabilityCheckExceptionThrowFactory, DuplicateNameFactoryA, DuplicateNameFactoryB, GetCacheStoreExceptionFactory, InitialFailureFactory, LocalFactory, MemoryFactory, NoAnnotationFactory, RemoteCacheFactory, ReturnNullFactory, SetCacheStoreFactory 增加测试用工厂;
[Add] META-INF/services/net.lamgc.cgj.bot.cache.CacheStoreFactory 添加 SPI 接口实现注册文件, 并注册测试用相关实现;
2020-11-14 11:02:35 +08:00
LamGC d4c208f2f7 [Optimize] Framework-API, Event 调整 EventUtils, Author 类以阻止被继承(和实例化);
[Optimize] Author 添加 final 关键字以阻止被继承;
[Optimize] EventUtils 添加 final 关键字和私有无参构造器以阻止被继承和实例化;
2020-11-13 20:52:18 +08:00
LamGC ba550bb16f [Update] Project 添加标签, 更新少量内容;
[Update] README.md 添加标签, 更新许可证相关.
2020-11-12 22:01:07 +08:00
LamGC c9a4af8121 [Update] CacheStore-API 应 CacheKey 更改, 完善相应单元测试;
[Update] CacheKeyTest 完善单元测试;
2020-11-12 21:40:21 +08:00
LamGC 80d47dd8cf [Change] CacheStore-API 调整 CacheKey 构造方法中的处理细节.
[Change] CacheKey 调整构造方法 '<init>(String, String...)' 中对 'keyStrings' 为 null 或 0 长度添加判断, 增加对 'keyStrings' 中包含 null 值的检查;
2020-11-12 21:33:52 +08:00
LamGC 76372adbf9 [Add] Project 添加一份尚未完成的 Readme;
[Add] README.md 添加尚未完成的 Readme;
2020-11-10 14:19:19 +08:00
LamGC 74455a1820 [Fix] Project 尝试移除对 Jacoco 测试报告生成路径的配置以修复 Codecov 无法收集报告的问题;
[Fix] Project/pom.xml 移除 Jacoco 的报告路径配置;
2020-11-09 21:56:24 +08:00
LamGC 0c7e5467c8 [Update] Project 更新 maven-test workflow 以添加 Codecov 测试覆盖率报告收集分析器;
[Update] maven-test.yml 添加 Codecov step;
2020-11-09 21:49:27 +08:00
LamGC 27c5b49479 [Update] Project 为项目 Maven 构建配置添加 JaCoCo 测试覆盖率报告生成插件;
[Update] Project/pom.xml 添加 JaCoCo 插件;
2020-11-09 21:44:50 +08:00
LamGC 7c9ad64cb7 [Add] Project 添加 Command-API, Command-pixiv 模块以分离 pixiv 与 Core 模块, 使命令组合更灵活;
[Add] Command-API, Command-pixiv 添加模块;
[Change] Core/pom.xml 调整依赖, 移除对 pixiv 模块的依赖, 添加 Command-API 模块依赖;
2020-11-09 18:42:30 +08:00
LamGC 4b97bf5803 [Update] Core 提升 JsonFrameworkDescriptorSerializerTest 测试覆盖率至 100%;
[Update] JsonFrameworkDescriptorSerializerTest 提升测试覆盖率;
[Add] badBotCode-MissingPatternsField-framework.json, badBotCode-PatternsFieldNonArray-framework.json 添加两种错误解析数据;
[Change] test-framework.json 调整解析数据以检查两种软错误解析情况;
2020-11-08 21:36:39 +08:00
LamGC 789c86406c [Fix] Core 修复 PluginDependencyJsonSerializer 中不可能出现的情况;
[Fix] PluginDependencyJsonSerializer 移除 'PluginVersionSupported != null' 的情况判断, 从 PluginDependency 的源码设计来讲一般不会出现这个问题;
2020-11-08 21:34:46 +08:00
LamGC f0e0956580 [Add] Framework-API 为 Author 添加仅 name 参数的构造方法;
[Add] Author 添加仅有 name 参数的构造方法 '<init>(String)';
2020-11-08 21:33:31 +08:00
LamGC 521fdd174c Merge pull request #36 from LamGC/add-contributing-guide
添加贡献指南(即使未完成)
2020-11-08 21:17:29 +08:00
LamGC 7024510df9 [Update] Project 修复贡献指南中的一些细节问题, 补充部分内容的细节;
[Update] .github/CONTRIBUTING.md 修复 Review 中指出的问题, 补充部分内容的细节;
2020-11-08 20:57:28 +08:00
LamGC 97d10fc1da [Update] Project 修复贡献准则中的标点符号错误,并补充警告信息;
[Update] .github/CONTRIBUTING.md 修复标点符号错误并补充警告信息细节;
2020-11-08 20:40:21 +08:00
LamGC 59095c67b5 [Update] Project 对贡献指南中针对提交信息里 GAV 坐标的使用说明;
[Update] .github/CONTRIBUTING.md 补充对 Commit Message 中 GAV 坐标的使用说明;
2020-11-08 20:36:06 +08:00
LamGC c029f149a4 [Update] Project 为贡献指南添加警告说明, 以表明该指南尚未完成;
[Update] .github/CONTRIBUTING.md 添加警告说明以表明该指南尚未完成, 有未提及事项需联系项目相关管理者;
2020-11-08 20:32:29 +08:00
LamGC b82bcd5e57 [Optimize] Core 修复 PluginDependencyJsonSerializer 代码规范上的错误(单行字符不超过 120 字符);
[Optimize] PluginDependencyJsonSerializer 修复代码规范上的问题(单行字符不超过 120 字符);
2020-11-08 20:30:54 +08:00
LamGC 311afd431b [Add] Framework-API 为 MessageSender 添加 getPlatform 方法以更好的标识一个 Sender, 并且补充 Javadoc;
[Add] MessageSender 添加 'getPlatform()' 方法以获取识别 Sender 的其中一个要素, 补充类文档;
2020-11-08 18:39:30 +08:00
LamGC f01d1ac8fd [Change] Core 为 DefaultFrameworkDescriptor 添加 @SuppressWarnings 注解以忽略不必要的警告信息;
[Change] DefaultFrameworkDescriptor 添加 @SuppressWarnings 注解以忽略不必要的警告信息;
2020-11-07 16:24:44 +08:00
LamGC ce31b92b25 [Update] Core 完善 JsonFrameworkDescriptorSerializerTest 单元测试;
[Add] badAuthor-InvalidField-framework.json, badAuthor-MissingField-framework.json, badAuthor-NonObject-framework.json 添加 author 属性错误解析数据;
[Add] badBotCode-NonObject-framework.json 添加 botCode 属性错误解析数据;
[Add] badPlatform-MissingField-Identify-framework.json, badPlatform-MissingField-Name-framework.json, badPlatform-NonObject-framework.json 添加 platform 属性错误解析数据;
[Add] badPluginDependency-NonPrimitive-framework.json, badPluginDependency-NonString-framework.json 添加 pluginDependency 属性错误解析数据;
[Add] JsonFrameworkDescriptorSerializerTest 添加多个异常情况的测试项;
[Add] test-framework.json 添加软错误(不影响解析过程)数据以检查软错误情况;
2020-11-07 16:23:31 +08:00
LamGC cc1ed81adc [Add] Core 添加一份 framework.json 完整示例文件;
[Add] simple-framework.json 添加一份 framework.json 完整示例文件;
2020-11-07 15:55:42 +08:00
LamGC 1eee7d19c6 [Change] CacheStore-Redis 修复 RedisConnectionPool 为所有 Factory 共用而导致多实例情况下会出现连接错误的问题;
[Change] RedisConnectionPool 将该类从静态方法类转变为实例类;
[Change] RedisCacheStore, RedisMapCacheStore, RedisSingleCacheStore 引入 RedisConnectionPool 实例以进行具体操作;
[Change] RedisMapCacheStoreTest, RedisSingleCacheStoreTest 调整 CacheStore 实例的获取, 不再自行创建具体实现, 转为通过 Factory 创建实例;
[Change] RedisCacheStoreFactory 单独创建一个 RedisConnectionPool 以提供给通过 Factory 自身创建的 CacheStore 实现;
2020-11-06 20:08:46 +08:00
LamGC dd7ac015f9 [Fix] Core 修复 FrameworkManager 中未适配 FrameworkFactory 更改导致的编译失败问题;
[Fix] FrameworkManager 适配 FrameworkFactory 的更改;
2020-11-05 13:38:50 +08:00
LamGC d92873c1f5 [Fix][Document] Event 修复 SupportedCancel 中 javadoc 内容的错误;
[Fix][Document] SupportedCancel 修复文档上的细节错误;
2020-11-05 13:32:18 +08:00
LamGC 79c12a8799 [Add] Event 为 Cancelable 添加 'cancel()' 方法;
[Add] Cancelable 添加 'cancel()' 方法以触发取消操作;
2020-11-05 00:59:03 +08:00
LamGC 2875426f72 [Add][Change] Framework-API, Core 添加 FrameworkContext 用于向 Framework 传递与 Bot 有关的相关对象;
[Add] FrameworkContext, DefaultFrameworkContext 添加 FrameworkContext 和一个默认实现;
[Change] Framework, FrameworkFactory 调整代码以支持传递 FrameworkContext;
2020-11-05 00:58:07 +08:00
LamGC 4a2337afd7 [Change] CacheStore-local, CacheStore-redis 适配 CacheStore-API 模块的更改;
[Change] LocalCacheStoreFactory 适配更改;
[Change] RedisCacheStoreFactory 适配更改, 增加 Redis 配置文件相关逻辑;
[Change] RedisConnectionPool 增加 Redis 连接配置功能;
2020-11-05 00:55:40 +08:00
LamGC b7d712da21 [Add][Change] Common 更改 CacheStoreBuilder 为实例模式, 为 CacheStoreFactory 添加 initial 方法;
[Add] CacheStoreFactory 添加 'initial(File)' 方法供缓存组件进行初始化;
[Change] CacheStoreBuilder 将 CacheStoreBuilder 从静态类更改为实例类, 增加对 CacheStoreFactory 的初始化;
[Change] CacheStoreBuilderTest 适配 CacheStoreBuilder 的更改;
2020-11-05 00:53:32 +08:00
LamGC 235c7452b8 [Add] Project, Event, Core, Framework-API 增加 Event 模块;
[Add] EventExecutor, EventObject, EventHandler 添加基本的事件类;
[Add] AbstractEventObject 添加完成了 EventObject 所需方法的抽象类;
[Add] EventHandlerRegistry 添加事件处理注册器接口, 分离与事件处理注册的部分;
[Add] Core/pom.xml, Event/pom.xml, Framework-API/pom.xml, Project/pom.xml 添加 Event 模块并在所需模块中引入;
[Add] Cancelable, SupportedCancel 添加与事件取消有关的接口;
[Add] EventUtils 添加与事件处理有关的工具类;
[Add] EventUtilsTest, TestEvent, StandardHandler, IllegalHandler 添加针对 EventUtils 的完整单元测试;
2020-11-04 22:50:46 +08:00
LamGC 0d80d21144 [Delete] Framework-Mirai 移除在 Mirai 新版本不再需要的依赖项;
[Delete] net.mamoe:mirai-japt 移除该依赖项;
2020-10-25 08:40:12 +08:00
LamGC 469c8050eb [Add] Framework-API 增加 Readme(半成品 :P);
[Add] Readme.md 添加一个尚未完成的 Readme;
2020-10-24 16:08:38 +08:00
LamGC 0af0647153 [Change] Project 将贡献指南中 'Commit Message' 章节升级为二级标题;
[Change] CONTRIBUTING.md 将 'Commit Message' 章节标题升级为二级标题;
2020-10-24 15:51:39 +08:00
LamGC ee4681cfe3 [Fix] Project 修复贡献指南中的一些细节错误;
[Fix] CONTRIBUTING.md 修复了一个换行错误,然后调整了一下格式;
2020-10-24 15:45:28 +08:00
LamGC 21f5373231 [Update] Project 更新贡献指南的依赖相关说明;
[Update] CONTRIBUTING.md 增加依赖相关部分, 调整其他内容的细节;
2020-10-24 15:40:03 +08:00
LamGC c94f3d83e7 [Optimize] Common 整理代码;
[Optimize] CacheStoreBuilder 整理代码格式;
2020-10-23 20:05:01 +08:00
LamGC 3a918fe3ee [Document][Deprecated] Framework-API 考虑弃用并移除 MessageSource 中的 unknown 类型;
[Document][Deprecated] MessageSource 对 UNKNOWN 标注弃用标识, 考虑对其移除;
2020-10-18 22:26:47 +08:00
LamGC 1ac7507868 [Update] Project 再更新一点点贡献者指南;
[Update] .github/CONTRIBUTING.md 稍微标注一下相关词汇;
2020-10-18 22:21:12 +08:00
LamGC 26887d9d63 [Update] Project 又更新一点点贡献指南(依然是未完成状态); 2020-10-18 22:14:31 +08:00
LamGC e2c6aca34c [Change] CacheStore-Local 调整 AutoCleanTimer 初始化细节, 将 AutoCleanTimer 对 Cleanable 的强引用改为弱引用;
[Change] AutoCleanTimer 简化 static 代码块内容, 移除 debug 相关代码(目前用不上);
[Change] AutoCleanTimer 对 Cleanable 的强引用改为弱引用, 以防止 CacheStore 无法被回收导致内存泄漏;
2020-10-18 14:34:01 +08:00
LamGC 11327a368f [Add] Framework-API 补充提交一个工具类;
[Add] CollectionUtils 添加集合工具类;
2020-10-17 22:50:31 +08:00
LamGC 0b6f537354 [Add][Fix] Framework-API 补充提交两个异常类;
[Add] NoSuchSenderException, UploadImageException 添加两个与 MessageSender 相关的异常类;
2020-10-17 22:45:50 +08:00
LamGC 551c2097be [Change] Framework-API 调整 Framework 在初始化失败后的行为;
[Change] Framework 在初始化失败后抛出异常中断构造;
2020-10-17 12:02:02 +08:00
LamGC 9e4a66ca70 [Change] CacheStore-local 更改组件名;
[Change] LocalCacheStoreFactory 更改组件名('local' -> 'local-memory');
2020-10-17 12:01:07 +08:00
LamGC 78b234237f [Add][Move][Change] Project, Common, Core 添加 Common 模块, 将 CacheStoreBuilder 转移到 Common 模块中;
[Add] net.lamgc:ContentGrabbingJi-Common 添加 Common 模块作为共用组件共享的模块;
[Move] CacheStoreBuilder, CacheStoreBuilderTest, FactoryInfo, NoSuchFactoryException 移动 Cache 相关类到 Common 模块中;
[Change] Core:pom.xml 添加 Common 模块的依赖, 调整测试项依赖;
2020-10-17 11:59:29 +08:00
LamGC ea3c596d6e [Add] Framework-API 添加消息发送器(或者说, 消息发送者对象);
[Add] MessageSender, MessageSenderFactory 添加 Sender 和对应工厂类;
[Add] MessageSource 添加消息源类型枚举类;
2020-10-16 09:57:22 +08:00
LamGC c8ef160f3a [Add][Change] Framework-API 调整 BotCode 接口, 单独抽出转换相关方法到转换器接口中, 添加一个 BotCode 抽象类;
[Add] BotCodeConverter 添加 BotCode 转换类(后续可能就不需要分化 BotCode 实现了?);
[Change] BotCode 抽出 BotCode 转换相关代码到 BotCodeConverter;
[Add] AbstractBotCode 添加一个实现了相关细节的 BotCode 抽象类;
2020-10-16 09:54:23 +08:00
LamGC f1df151c89 [Fix] Project 升级 JUnit 4 版本号以修复潜在的漏洞;
[Fix] junit:junit 升级版本以修复潜在的漏洞(GHSA-269G-PWP5-87PP; CVE-2020-15250) (4.13 -> 4.13.1);
2020-10-14 18:57:26 +08:00
LamGC 9d220d1066 [Add] Framework-API 添加一个初定的 ResourceBundle 工厂接口, 以允许框架指定 ResourceBundle;
[Add] ResourceBundleFactory 添加初定的 ResourceBundle 工厂接口;
2020-10-11 03:58:16 +08:00
LamGC f57871f020 [Fix] CacheStore-API 修复 FactoryPriority 可以被实例化的问题;
[Fix] FactoryPriority 添加私有构造方法以阻止实例化;
2020-10-09 00:46:27 +08:00
LamGC 1cf4c9e47b [Add][Document] Framework-Mirai 添加 Readme 文档;
[Add] README.md 添加一份尚未完成的 Readme 文档;
2020-10-09 00:38:56 +08:00
LamGC bcf09b6af7 [Update][Document] CacheStore-API 更新 Readme, 补充文档内容;
[Update] README.md 增加一些细节内容;
2020-10-09 00:37:20 +08:00
LamGC 17808c8d8f [Change] Framework-API 为 Framework 增加对 Descriptor 的检查;
[Change] Framework 构造函数增加对 Descriptor 的检查;
2020-10-09 00:35:25 +08:00
LamGC c4c452cf84 [Update] Framework-Mirai 跟进 Mirai 框架的更新;
[Update] net.mamoe:mirai-core, net.mamoe:mirai-core-qqandroid 更新依赖项版本(1.2.2 -> 1.3.2);
2020-10-07 16:49:59 +08:00
LamGC 7744e0e82e [Change] Framework-API, Core 将 PluginManager 相关迁移至 Core 模块;
[Change] * 从 Framework-API 迁移至 Core 模块;
2020-10-07 16:48:13 +08:00
LamGC 2bea395cf7 [Add] Pixiv 添加一组与 Pixiv 登录会话相关的类;
[Add] PixivLogin, PixivLoginException 初版的登录器接口;
[Add] PixivSession, BasicPixivSession Pixiv 帐号登录会话抽象类和一个默认基本实现;
[Add] ObjectInputStreamLogin 通过反序列化 CacheStore 进行登录的登录器;
2020-10-07 14:14:16 +08:00
LamGC 52d015a4ba [Add] Framework-API 添加 framework.json 解析功能单元测试;
[Add] JsonFrameworkDescriptorSerializerTest 将 Gson 配置从 JsonFrameworkDescriptorFinder 抽离进行单独测试;
[Add] test-framework.json 一个完整的测试用框架配置文件;
2020-10-07 09:11:25 +08:00
LamGC 48d51b9da5 [Add] Framework-API 补充三个异常类;
[Add] BuildBotCodeException, InvalidBotCodeException, UnsupportedBotCodeException 添加三种与功能码相关的异常类;
2020-10-07 08:47:33 +08:00
LamGC 8dfaa97040 [Add] Framework-API 添加基于 PF4J 的框架API初版;
[Add] Framework, FrameworkDescriptor, FrameworkManager 添加框架相关类;
[Add] AuthorJsonSerializer, BotCodeDescriptorJsonSerializer, PlatformJsonSerializer, PluginDependencyJsonSerializer 添加一组辅助解析 FrameworkDescriptor 的 Json 序列化工具类;
[Add] Author, BotCodeDescriptor, Platform 添加与框架相关的信息类;
[Add] BotCode 添加机器人功能码接口, 以抽象化各框架实现的功能码;
[Add] DefaultFrameworkDescriptor 添加 FrameworkDescriptor 的默认实现(应该不会再改的了);
[Add] JsonFrameworkDescriptorFinder 添加自定义的 Json 框架元数据查找解析器;
[Add] FrameworkFactory 添加用于构造框架对象的工厂类;
[Add] org.pf4j:pf4j 添加 PF4J 作为 Framework-API 的插件框架;
[Add] com.google.code.gson:gson 添加 Gson 作为框架元数据的解析工具;
2020-10-07 00:17:29 +08:00
LamGC 0841924b13 [Add] Exec 添加一个预期(?)的启动模块;
[Add] ApplicationMain 程序主入口, 负责初始化项目运行环境和启动项目;
[Add] log4j2.xml 使用上一版日志配置, 后续会有调整;
[Add] cgj_setting.json 初始配置文件, 需等 Core 模块完成后才会具体编写;
2020-10-06 10:33:13 +08:00
LamGC c27d072e68 [Fix][Update] CacheStore-redis, Core 修复缓存组件的逻辑问题, 调整 Core 中对于 CacheStoreBuilder 的测试用例;
[Fix] CacheStoreBuilder 修复 Factory.canGetCacheStore 返回 false 被无视的问题;
[Fix] RedisConnectionPool 修复因变量未及时更新导致的逻辑异常;
[Update] CacheStoreBuilderTest 调整测试用例以更具体的检查获取的缓存实现类与预期是否相同;
2020-10-06 10:28:45 +08:00
LamGC b017599eae [Update] Core 增加 CacheStore 存储源过滤;
[Add] FactoryInfo 增加 source 属性;
[Add] CacheStoreBuilder 增加对存储源的指定;
[Add] CacheStoreBuilderTest 调整测试用例以对存储源过滤进行测试(但该测试依然是不完善的, 有待改进);
2020-10-02 10:11:56 +08:00
LamGC 097c4a035c [Update] CacheStore-local, CacheStore-redis 适配 CacheStore-api 的更改;
[Update] LocalCacheStoreFactory 根据 CacheStore-local 中实现的特性, 设置 Factory 注解中 source 参数;
[Update] RedisCacheStoreFactory 根据 CacheStore-redis 中实现的特性, 设置 Factory 注解中 source 参数;
2020-10-01 01:02:02 +08:00
LamGC 75f19e5d21 [Add] CacheStore-api 增加对缓存存储源类型的标注, 以协助 Core 按需选择缓存容器;
[Add] CacheStoreSource 增加缓存存储源类型枚举类, 暂时添加目前已涉及的类型;
[Update] Factory 增加 CacheStoreSource 项目(因为 CacheStoreSource 是没有更改需要的;
2020-10-01 00:58:40 +08:00
LamGC 1643891166 [Change] CacheStore-redis 调整内部类 RedisConnectionPool 的访问修饰符;
[Change] RedisConnectionPool 调整访问修饰符(public -> default);
2020-09-29 10:11:11 +08:00
LamGC 1c634af225 [Add][Document] CacheStore-API 更新 Javadoc 并添加初版 Readme.md;
[Add] README.md 增加子模块的 Readme;
[Document] CollectionCacheStore 更新 Javadoc;
2020-09-29 10:10:01 +08:00
LamGC 7d7085f8c5 [Update] Core, Pixiv 更新 Gson 版本号;
[Update] Core - com.google.code.gson:gson 更新版本号(2.8.5 -> 2.8.6);
[Update] Pixiv - com.google.code.gson:gson 更新版本号(2.8.5 -> 2.8.6);
2020-09-28 23:12:01 +08:00
LamGC 7fcef96b0a [Add][Change] Core 增加 Converter 并增加对 FactoryName 不允许重复的限制;
[Add] GsonJsonObjectConverter, GsonJsonArrayConverter 增加对 Gson 中 JsonArray, JsonObject 的转换;
[Change] CacheStoreBuilder, FactoryInfo 增加对 FactoryName 的不重复限制, 增加对 FactoryInfo 的 hashCode, equals 方法实现(以 FactoryName 字段为主);
[Add] com.google.code.gson:gson:2.8.5 增加对 Gson 的依赖(解析数据, 后续可能会转移);
2020-09-28 23:02:50 +08:00
LamGC cd3d5e42eb [Add] Project 将 log4j2 作为项目测试时的 Logger 实现;
[Add] org.apache.logging.log4j:log4j-slf4j-impl 使用 log4j2 作为测试时的日志实现;
2020-09-23 13:55:45 +08:00
LamGC 4d8e0af7a5 [Change] CacheStore-redis 调整 Factory 对获取尚未完成组件的反馈方式;
[Change] RedisCacheStoreFactory 调整 List 和 Set 的行为(返回 null 改为抛出异常);
2020-09-23 13:53:41 +08:00
LamGC 4812eec2d7 [Change] Core 修复代码规范上的问题;
[Change] CacheStoreBuilder 调整 loadFactory 中 ServiceLoader 类型变量的命名(FACTORY_LOADER -> factoryLoader);
2020-09-23 13:51:25 +08:00
LamGC 7313b832b0 [Add] Project 添加贡献指南;
[Add] CONTRIBUTING.md 添加一个尚未完成的贡献指南,完成后合并入 3.0.0 分支;
2020-09-23 12:55:56 +08:00
LamGC 8e5df3b5ae [Add] Project 添加贡献者公约;
[Add] CODE_OF_CONDUCT.md 添加贡献者公约文件;
2020-09-23 12:14:39 +08:00
LamGC 3e591e8c26 [Add] Core 添加内部 CacheStoreBuilder;
[Add] CacheStoreBuilder 添加 Builder 以统一使用所有 Factory 创建 CacheStore;
[Add] FactoryInfo Factory 注解的信息实体类;
[Add] NoSuchFactoryException 当无可用 Factory 时的自定义异常;
[Add] CacheStoreBuilderTest 尚不完善的单元测试类, 仅测试有效情况, 因 SPI 机制原因, 不方便编写多个情况的测试用例;
2020-09-23 08:00:11 +08:00
LamGC 631c11b659 [Add] Project 添加排除 logs 文件夹;
[Add] logs/ 添加对 logs 文件夹的排除;
2020-09-23 07:52:06 +08:00
LamGC c6bbf157d3 [Add] Project 添加 Github Action 以确定提交单元测试状况;
[Add] .github/workflows/maven-test.yml 添加用于测试的 Action 配置;
2020-09-12 23:50:20 +08:00
LamGC 96f9baf5bc [Add] Core 在找到更好的方法前, 先通过间接依赖两个缓存实现来提供给 Core 进行缓存相关测试;
[Add] net.lamgc:ContentGrabbingJi-CacheStore-local 添加 local 缓存实现依赖项(仅 test);
[Add] net.lamgc:ContentGrabbingJi-CacheStore-redis 添加 redis 缓存实现依赖项(仅 test);
2020-09-12 03:17:48 +08:00
LamGC c856d94384 [Add][Update] CacheStore-redis 添加 Redis 缓存存储容器的实现模块;
[Add] META-INF/services/* 支持 SPI 机制;
[Add] RedisCacheStore, RedisCacheStoreFactory, RedisConnectionPool, RedisUtils 添加相关父类和辅助类;
[Add] RedisMapCacheStore, RedisMapCacheStoreTest 添加映射表缓存相关实现及完整单元测试;
[Add] RedisSingleCacheStore, RedisSingleCacheStoreTest 添加单项缓存相关实现及完整单元测试;
[Update] redis.clients:jedis 更新依赖项版本(3.2.0 -> 3.3.0);
2020-09-12 03:15:37 +08:00
LamGC 862ddc871e [Add] CacheStore-local 适配 SPI 机制;
[Add] META-INF/services/* 增加接口实现声明, 以支持 SPI 机制;
2020-09-12 03:11:59 +08:00
LamGC 10c92d8812 [Update] CacheStore-local 适配更改;
[Update] LocalCacheStoreFactory 适配 CacheStore-api 对 CacheStoreFactory 的更改;
2020-09-12 02:59:40 +08:00
LamGC 4f877a03c2 [Add][Change] CacheStore-api 调整 CacheStoreFactory 的方法信息;
[Add] Factory 添加注解以设置固定信息;
[Add] FactoryPriority 添加优先级常量类;
[Add] GetCacheStoreException 增加异常以表示无法获取 CacheStore 对象;
[Change] CacheStoreFactory 增加'canGetCacheStore'方法, 调整方法签名以允许抛出异常;
2020-09-12 02:58:59 +08:00
LamGC 9a3f8698d3 [Add] CacheStore-api 对 CacheKey 增加新的构造方法, 补充完整的单元测试;
[Add] CacheKey 添加构造方法 '<init>(String[])';
[Add] CacheKeyTest 添加针对 CacheKey 的完整单元测试;
2020-09-05 13:31:00 +08:00
LamGC 3c705ee16a [Update] CacheStore-local 适配因加入 CacheKey 带来的变更;
[Update] * 适配更改;
2020-09-05 11:32:52 +08:00
LamGC a24b810183 [Change] CacheStore-api 增加 CacheKey 以替代 String 作为 key 参数类型;
[Add] CacheKey 增加用于代表缓存项键的对象(可被缓存具体实现根据存储需要调整最终 key 的格式, 例如 Redis 的分隔符问题);
[Change] CacheStore, CollectionCacheStore, ListCacheStore, MapCacheStore, SingleCacheStore 适配更改;
2020-09-05 11:30:27 +08:00
LamGC d650f12626 [Add] 增加自动清理机制;
[Add] AutoCleanTimer, Cleanable 增加定时清理器和所需实现的接口;
[Update] HashCacheStore 实现了 Cleanable 接口, 并使用了一种简单但效率较低的遍历清除方法, 有待改进;
2020-09-04 23:36:23 +08:00
LamGC 565049f393 [Delete] 从项目父 POM 中移除有关 Slf4j 实现的依赖项(最终依赖项交给 exec 模块);
[Delete] org.apache.logging.log4j:log4j-slf4j-impl 从父 POM 中移除该日志实现依赖项;
2020-09-04 23:32:56 +08:00
LamGC 06c3af9350 [Change] 补充 HashMapCacheStore 对参数的 null 检查;
[Change] HashMapCacheStore 增加对参数的 null 检查, 及时抛出 NPE;
2020-09-04 23:30:35 +08:00
LamGC 455f922cca [Document] 更新 SetCacheStore 的一些说明;
[Document] SetCacheStore 更新说明;
2020-09-04 23:27:37 +08:00
LamGC 49a6257148 [Add] CacheStore-local 添加 SetCacheStore 的实现, 添加 CacheStoreFactory 的实现;
[Add] HashSetCacheStore 添加基于 HashSet 的 SetCacheStore 实现;
[Add] LocalCacheStoreFactory 增加本地缓存库的 CacheStoreFactory;
2020-09-04 20:02:36 +08:00
LamGC 688b99d198 [Add] 添加 MapCacheStore 的本地实现;
[Add] HashMapCacheStore 添加基于 Hashtable 的 MapCacheStore 实现;
[Add] HashMapCacheStoreTest 添加针对 HashMapCacheStore 的完整单元测试;
2020-09-04 19:58:45 +08:00
LamGC ba422ef90e [Document] 调整 SetCacheStore 文档;
[Document] SetCacheStore 补充文档细节;
2020-09-04 19:22:27 +08:00
LamGC a7495156ff [Add][Fix] 添加新的CacheStore子类型, 修复 CacheStoreFactory 方法命名错误;
[Fix] CacheStoreFactory 修复 newMapCacheStore():SingleCacheStore 方法命名错误(更名为 newSingleCacheStore);
[Add] MapCacheStore 初步添加映射表型缓存存储容器接口;
[Add] CacheStoreFactory 增加对 MapCacheStore 的获取方法;
2020-09-04 18:45:32 +08:00
LamGC 372363085d [Optimize] 整理单元测试代码;
[Optimize] HashSingleCacheStoreTest, ListCacheStoreTest 整理代码;
2020-09-04 17:51:02 +08:00
LamGC 003f571aad [Add] 添加 ListCacheStore 的本地实现;
[Add] CopyOnWriteArrayListCacheStore 添加基于 CopyOnWriteArrayList 的 ListCacheStore 实现;
[Add] LocalCollectionCacheStore 添加集合型本地缓存存储容器父类, 已实现 CollectionCacheStore 接口;
[Add] ListCacheStoreTest 添加对 CopyOnWriteArrayListCacheStore 的完整单元测试(同样包括了 LocalCollectionCacheStore 的测试);
2020-09-04 00:29:36 +08:00
LamGC 7448379da0 [Document] 优化文档;
[Document] 优化 CacheStore-Api 模块的文档;
2020-09-04 00:25:01 +08:00
LamGC 7461376141 [Add] 添加基于 Hashtable 的本地缓存存储容器;
[Add] HashCacheStore 添加 CacheStore 的抽象实现;
[Add] HashSingleCacheStore 添加 SingleCacheStore 的具体实现;
[Add] HashSingleCacheStoreTest 增加完整测试项(涵盖了 HashCacheStore);
2020-09-03 19:27:08 +08:00
LamGC 9f08ca0eba [Rename] 调整 SingleCacheStore 某个命名;
[Rename] SingleCacheStore 更改'setIfNoExist'为'setIfNotExist';
2020-09-03 19:25:30 +08:00
LamGC c02336b0b6 [Add] 添加 junit 依赖项;
[Add] junit:junit 添加依赖项;
2020-09-03 18:36:19 +08:00
LamGC ca6ee0972f [Optimize][Change] 移除不必要的注解, 更改 Factory 中的泛型标记为合适的标记;
[Optimize] SingleCacheStore 移除不必要的语法规范检查注解(My bad :P);
[Change] CacheStoreFactory 更改'newListCacheStore'和'newSetCacheStore'的泛型标记('V(value)' -> 'E(element)');
2020-09-03 18:13:28 +08:00
LamGC 3ecaf4db67 [Change] 调整CollectionCacheStore泛型声明以更好的开发对应实现;
[Change] CollectionCacheStore 增加 "C (Collection)" 泛型标记, 使下级接口能具体声明其 Collection 的具体类型;
[Change] ListCacheStore, SetCacheStore 适配更改;
2020-09-03 18:04:17 +08:00
LamGC e98fd82916 [Add][Moved] 调整Converter包路径, 增加StringToStringConverter;
[Moved] Converter, StringConverter 调整包路径;
[Add] StringToStringConverter 添加简易StringConverter实现;
[Change] CacheStoreFactory 适配更改;
2020-09-03 18:01:18 +08:00
LamGC 6502385641 [Change][Fix] 调整 CollectionCacheStore 部分命名, 调整泛型声明;
[Change] CollectionCacheStore 调整命名, 由 value/values 更名为 element/elements;
[Fix] ListCacheStore, SetCacheStore 修复因泛型错误导致的类型错误问题;
2020-09-03 15:40:10 +08:00
LamGC 74904cdfdb [Add] 为 Core 模块添加 CacheStore-API 模块;
[Add] net.lamgc:ContentGrabbingJi-CacheStore-api 添加模块依赖项;
2020-09-03 14:30:21 +08:00
LamGC 1420cd337b [Fix] 修复 CollectionCacheStore#clearCollection 没有 key 参数的问题;
[Fix] CollectionCacheStore 为 clearCollection 方法添加 key 参数以指定缓存项;
2020-09-03 14:29:27 +08:00
LamGC 6f8e03baf5 [Add] 添加CacheStore-API;
[Add] CacheStore 添加缓存存储容器父接口;
[Add] CollectionCacheStore, SetCacheStore, ListCacheStore 添加集合型缓存存储容器接口;
[Add] SingleCacheStore 添加单项缓存存储容器接口;
[Add] Converter, StringConverter 添加数据类型转换器接口;
[Add] CacheStoreFactory 添加CacheStore工厂接口;
2020-09-03 14:03:18 +08:00
LamGC 2e78dd5931 [Add] 初始化组成模块; 2020-09-03 13:46:16 +08:00
LamGC 55157c58c5 [Initial] Initial Commit;
[Add] .gitignore 添加git忽略规则文件;
[Add] LICENSE 添加 AGPLv3 开源许可证协议;
2020-09-03 13:24:18 +08:00
278 changed files with 14517 additions and 9333 deletions
+103
View File
@@ -0,0 +1,103 @@
# 贡献指南 #
> 警告:该指南尚未完成,所以在遇到与贡献有关问题时应该及时联系项目管理者以确定有关贡献事项准则,
如该指南未提及有关事项时,你不应该擅作主张地决定事项准则,擅作主张将会在 Review 阶段造成不必要的麻烦(甚至会被拒绝)。
!!!这条消息将在贡献指南完善的差不多后去除。!!!
欢迎你为本项目作出贡献!本项目的发展离不开所有贡献者(不仅仅只是提交了相关代码,帮助编写文档,还是提出问题或想法也算是贡献者之一!)的贡献。
在为本项目作出任何贡献之前,请先阅读[贡献者准则](https://github.com/LamGC/ContentGrabbingJi/blob/3.0.0/CODE_OF_CONDUCT.md)
一旦你尝试(或正在)为本项目贡献(贡献中,或者贡献过),即代表你**认同并接受**贡献者准则,**清楚明白且接受**因**违反贡献者准则**所造成的后果。
## 代码规范 ##
本项目代码规范大部分遵循 《Alibaba Java Coding Guidelines》,
详细内容可前往 https://github.com/alibaba/p3c 查阅。
如果你的 IDE 为 Eclipse 或 Idea,则推荐你为 IDE 添加阿里巴巴根据该规范开发的规范检查插件,该插件有助于你保持代码规范。
## Commit Message ##
提交信息需要遵循以下格式:
```
[TAG] ModuleName 对提交的说明;
[TAG] FileName/ClassName 对该文件的说明;
...
```
#### 标点符号的使用 ####
在提交信息中,符号使用**英文符号**。
#### 文件名或类名的说明 ####
对于类代码文件而言,FileName 只需要填写类名,对其他文件则需要包括后缀名。
FileName 和 ModuleName 可按需要使用`, `追加,注意与说明空一格,
例如: `[Add][Change] SimpleClass, MainClass 添加 SimpleClass 并调整 MainClass 的日志格式;`
ModuleName 根据修改的相关子模块名填写即可,对项目而非子模块的更改,则 ModuleName 为 `Project`,一个例子:
```
[Change][Update][Optimize] Project, SubModule01, WebFrontend 更新项目中的Readme, 修改 SubModule01 中鉴权的实现细节, 优化Web前端模块的启动速度;
[Update] Readme.md 调整文档格式;
[Change] AuthorizationChecker 调整鉴权过程以避免潜在的错误;
[Optimize] MainClass 优化初始化速度, 移除不必要的检查步骤;
```
#### 标签的使用 ####
目前支持以下Tag:
- `Add` 文件添加
- `Change` 文件更改
- `Update` 更新文件
- `Delete` 文件删除
- `Document` 文档相关
- `Fix` 问题修复
- `Optimize` 优化相关(或者代码整理啥的)
- `Move` 文件移动(例如包更改)
- `Rename` 更改名称(类名, 文件名等)
- `Issue:Id` 指定 Commit 所关联的 Github Issue(如果与其他标签有关,可直接加到指定标签后方,该标签可不用加)
- `PR:ID` 指定 Commit 所关联的 Pull Request(可选的,可直接使用 Github 的默认 Commit Message
> 注意:后续如有需要,会追加一些标签。
对大部分文件而言,FileName 按照以上规则填写即可,但对于依赖项而言,则要按照 GAVGroupId:ArtifactId:Version)坐标去除 **Version** 项后填写,
例如:`[Add] junit:junit 添加 Junit 单元测试依赖项;`
或者更新依赖项需要添加对依赖项版本号的变更:`[Update] junit:junit 更新 Junit 以修复潜在的漏洞('4.13' -> '4.13.1');`
#### 引用 Github 中的相关 Issue 或者 PR ####
如需引用相关 Issue 或者 PRPull Request),可在相关 Tag 中引用,或者另起一个 Tag 并声明指定的 ID,例如:
```
[Fix #11] Module 修复了部分功能异常情况;
...
```
或者是:
```
[Change][Issue #15] Module 增加了一些功能;
...
```
对于需要修复的相关 Issue,既可以采用第一种形式,也可以用第二种形式,单独添加一个 Issue 标签标明关联的 Issue 即可,比如这样:
```
[Fix][Issue #17] Module 修复了返回异常的问题;
...
```
如确定修复完成,也可以直接使用 Github 会对其作出相关操作的词(?),比如:
```
[Fix close#20] Module 修复响应码判断错误的问题;
...
```
或者是
```
[Fix][Issue fixed#28] Module 修复模板编译错误的问题;
...
```
## 代码质量 ##
如果你想添加新的功能,请务必对新功能相关代码编写好覆盖全,情况完善的单元测试(尽可能的覆盖所有新增功能,确保对大部分情况都有足够的测试)!
覆盖全、多角度、高度完善的单元测试是保证代码、项目质量必不可少的!
每次提交后,请检查 Action,查看 Github 中的代码是否能通过所有的单元测试。
## 依赖管理 ##
> 请**尽可能避免引入**新依赖项,如果某个依赖项的引入,仅仅是用于某些计算(或某个小功能,比如仅使用 Apache Commons 中的 `RandomUtils` 类获取 long 类型的随机数),
则这个依赖项的引入是完全没有必要的。
对于因极少需要而引入某些依赖项时,请考虑了解依赖项内的*实现细节*,并复现在项目中,以避免引入依赖项。
(当然,这是有例外的,如果某个功能较为复杂,且在该依赖项拥有很好的质量保证时,可考虑引入,具体细节可发起**issue**与我们进行讨论。)
**另外!**引入依赖项请注意**License**,确保依赖项可以被本项目使用。
-26
View File
@@ -1,26 +0,0 @@
---
name: Bug Report
about: Use this template to feedback bugs.
title: ''
labels: bug
assignees: ''
---
## Environmental information ##
OS(e.g: Windows 10 1909):
Java(e.g: Oracle Jdk 8.242):
Issue version(versionTag or commitId):
## Problem description ##
// Describe the problem in as much detail as possible here
## Expected behavior ##
// What will this function do under normal circumstances?
## Actual behavior ##
// But what does this feature actually look like?
## Recurrence steps ##
// What can we do to recreate this situation?
1.
+39
View File
@@ -0,0 +1,39 @@
# This workflow will build a Java project with Maven
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
name: Unit test confirmation of commit
on:
push:
branches: [ 3.0.0 ]
pull_request:
branches: [ 3.0.0 ]
jobs:
buildAndTest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Set up Redis Server
# You may pin to the exact commit or the version.
# uses: supercharge/redis-github-action@8dd3c86cd02fabe1bc459d55ba892a9ce91e23c6
uses: supercharge/[email protected]
with:
# Redis version to use
redis-version: latest
- name: Build with Maven
run: mvn -B test --file pom.xml
# - name: Upload Coverage Report to Codecov
# uses: codecov/codecov-action@v1
# with:
# token: ${{ secrets.CODECOV_TOKEN }}
# file: .*?/target/jacoco.exec$
# flags: Unit tests
# name: Jacoco-Converage-Report
# fail_ci_if_error: true
# verbose: true
+32 -12
View File
@@ -1,16 +1,36 @@
# Ignore test date folder
/pluginData/
/logs/
/cookies.store
/target/
# Compiled class file
*.class
# Ignore Idea files
# Log file
/logs/
*/logs/
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
# IDEA ide files
/.idea/
*.iml
# Ignore Visual Studio Code files
.classpath
.factorypath
.project
/.settings/
/.vscode/
# maven build directory
target/
# Test run directory
/test/
*/test/
+76
View File
@@ -0,0 +1,76 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project, and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at [[email protected]](mailto:[email protected]). All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
@@ -0,0 +1,16 @@
# CacheStore-API #
如需开发更多缓存组件,至少需要实现以下接口:
- CacheStore / CollectionCacheStore
- `CacheStore` 是所有缓存容器的父接口。
- 这两个类为抽象接口,定义了部分具体接口的公共方法。
- 不一定要**单独**实现该接口,可以**直接实现**具体的接口(比如 `SingleCacheStore`
- CacheStoreFactory
- 你还需要为其添加 `@Factory` 注解,否则不会生效。
- SingleCacheStore / MapCacheStore / SetCacheStore / ListCacheStore
- `MapCacheStore``SetCacheStore``ListCacheStore``CollectionCacheStore` 的子类。
- 至少需要提供其中一种实现才能算是一个有效的缓存组件。尚未实现的部分将会由其他组件代替。
完成缓存组件的开发后,应按照 SPI 机制的要求,设置所属 CacheStoreFactory 为 Service。
正常情况下,该模块不需要进行更改,即使需要更改,也需要保证向后兼容性。
+32
View File
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (C) 2021 LamGC
~
~ ContentGrabbingJi is free software: you can redistribute it and/or modify
~ it under the terms of the GNU Affero General Public License as
~ published by the Free Software Foundation, either version 3 of the
~ License.
~
~ ContentGrabbingJi is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU Affero General Public License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ContentGrabbingJi</artifactId>
<groupId>net.lamgc</groupId>
<version>3.0.0-alpha-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ContentGrabbingJi-CacheStore-api</artifactId>
</project>
@@ -0,0 +1,114 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache;
import java.util.Arrays;
import java.util.Objects;
/**
* 缓存键.
* 可根据不同缓存实现的需要调整名称.
* @author LamGC
*/
public final class CacheKey {
/**
* 默认分隔符.
*/
public final static String DEFAULT_SEPARATOR = ".";
private final String[] keys;
/**
* 创建一个缓存键名.
* @param first 组成键名的第一个部分.
* @param keyStrings 键名的其余组成部分.
* @throws NullPointerException 当 keyStrings 为 null 时抛出.
*/
public CacheKey(String first, String... keyStrings) {
Objects.requireNonNull(first);
if (keyStrings == null || keyStrings.length == 0) {
this.keys = new String[] {first};
} else {
checkKeyStrings(keyStrings);
this.keys = new String[keyStrings.length + 1];
this.keys[0] = first;
System.arraycopy(keyStrings, 0, this.keys, 1, keyStrings.length);
}
}
/**
* 提供一组字符串用于组成缓存键.
* @param keyStrings 键名组成数组.
*/
public CacheKey(String[] keyStrings) {
Objects.requireNonNull(keyStrings);
checkKeyStrings(keyStrings);
if (keyStrings.length == 0) {
throw new IllegalArgumentException("Provide at least one element that makes up the key");
}
this.keys = keyStrings;
}
private void checkKeyStrings(String[] keyStrings) {
for (String keyString : keyStrings) {
if (keyString == null) {
throw new NullPointerException("KeyStrings contains null");
}
}
}
/**
* 获取组成 Key 的字符串数组.
* @return 返回用于组成 Key 的字符串数组.
*/
public String[] getKeyArray() {
return keys;
}
/**
* 使用指定分隔符组成完整 Key.
* @param separator 分隔符.
* @return 返回组装后的完整 Key.
*/
public String join(String separator) {
return String.join(separator, keys);
}
@Override
public String toString() {
return join(DEFAULT_SEPARATOR);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
CacheKey cacheKey = (CacheKey) o;
return Arrays.equals(keys, cacheKey.keys);
}
@Override
public int hashCode() {
return Arrays.hashCode(keys);
}
}
@@ -0,0 +1,92 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache;
import java.util.Set;
/**
* 缓存存储容器.
* 缓存库之间不应该出现干扰的情况, 即使允许在实现上共用一个存储.
* @author LamGC
*/
public interface CacheStore<V> {
/**
* 设置指定缓存的生存时间.
*
* <p>当该缓存项的 TTL 为 0 时, 该缓存项将会失效, 并被删除.
* 关于删除失效缓存项的时机在此并不特别规定, 依照各实现自行处理.
* @param key 欲设置过期时间的缓存项键名.
* @param ttl 有效期时间, 如果设为 -1 则代表清除缓存项的 TTL, 缓存项不会因为 TTL 为 0 而失效. 单位"毫秒(ms)".
* @return 如果设置成功, 返回 true, 如果设置失败, 或缓存项不存在, 返回 false.
* @throws NullPointerException 当 key 为 null 时抛出.
*/
boolean setTimeToLive(CacheKey key, long ttl);
/**
* 查询指定缓存项的 TTL.
* @param key 欲查询 TTL 的缓存项键名.
* @return 如果缓存项存在且已设置 TTL, 则返回当前剩余 TTL, 如果缓存项不存在或未设置 TTL, 返回 -1.
* @throws NullPointerException 当 key 为 null 时抛出.
*/
long getTimeToLive(CacheKey key);
/**
* 获取当前缓存项数量.
* @return 返回缓存项数量.
*/
long size();
/**
* 清空缓存存储容器.
* @return 操作成功返回 true.
*/
boolean clear();
/**
* 检查指定缓存项是否存在.
*
* <p>如果缓存项不存在或失效(比如因 TTL 为 0 而失效), 则会判定为不存在.
* 缓存项失效不代表不存在(因为根据实现的不同, 可能还没来得及清理失效缓存项),
* 但缓存项不存在一定是失效的(即便如此, 缓存项一旦失效, 便不可获取).
*
* <p>故后续除特殊情况使用"缓存项 失效"描述, 文档将以"缓存项不存在"描述缓存项失效或不存在的情况.
* @param key 缓存项键名
* @return 如果存在, 返回 true, 如果不存在或失效, 返回 false.
* @throws NullPointerException 当 key 为 null 时抛出.
*/
boolean exists(CacheKey key);
/**
* 删除指定缓存.
* @param key 欲删除的缓存项键名.
* @return 如果存在并删除成功, 返回 true.
* @throws NullPointerException 当 key 为 null 时抛出.
*/
boolean remove(CacheKey key);
// 文档没有硬性要求"Set 中不能存在失效缓存项"的原因是因为: 即便确保了当时获取到的 Set 没有失效缓存项,
// 但是如果在获取后立刻出现失效缓存项了呢? 这个情况下依然不能保证没有失效的缓存项, 故不在文档中做出该硬性要求.
// 虽然是没说吧, 但是在返回时依然需要确保不会有过多失效缓存项的情况, 且获取时务必检查缓存项是否存在, 不要认为 Set 中的缓存项必然存在!
/**
* 获取缓存存储容器中的所有缓存项键名.
* @return 返回存储了所有缓存项键名的 Set 对象.
*/
Set<String> keySet();
}
@@ -0,0 +1,94 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import net.lamgc.cgj.bot.cache.exception.GetCacheStoreException;
import java.io.File;
/**
* 缓存存储容器构造工厂.
*
* <p>可支持不同实现缓存存储容器.
* @author LamGC
*/
public interface CacheStoreFactory {
/**
* Factory 初始化方法.
* <p> 当 Factory 被加载且验证正确后, 将会在第一次获取 CacheStore 前被调用一次(也可能在验证完成后执行).
* <p> 该方法仅调用一次, 之后将不再调用.
* @param dataDirectory 数据存储目录, 可在该目录内存储配置, 或持久化缓存.
* @throws Exception 上层管理类允许捕获任何抛出的异常, 如果 Factory 抛出异常, 将视为失效, 不被使用.
*/
void initial(File dataDirectory) throws Exception;
/**
* 获取一个新的 CacheStore 对象.
* @param identify 缓存标识.
* @param converter 类型的转换器.
* @return 返回 CacheStore 对象.
* @throws GetCacheStoreException 当 Factory 无法返回 CacheStore 对象时抛出, 需说明失败原因.
*/
<V> SingleCacheStore<V> newSingleCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException;
/**
* 获取一个新的有序列表缓存存储容器.
* @param identify 缓存标识.
* @param converter 元素类型与 String 的转换器.
* @param <E> 元素类型.
* @return 返回新的有序列表缓存存储容器.
* @throws GetCacheStoreException 当 Factory 无法返回 CacheStore 对象时抛出, 需说明失败原因.
*/
<E> ListCacheStore<E> newListCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException;
/**
* 获取一个新的无序集合缓存存储容器.
* @param identify 缓存标识.
* @param converter 元素类型与 String 的转换器.
* @param <E> 元素类型.
* @return 返回新的无序集合缓存存储容器.
* @throws GetCacheStoreException 当 Factory 无法返回 CacheStore 对象时抛出, 需说明失败原因.
*/
<E> SetCacheStore<E> newSetCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException;
/**
* 获取一个新的映射表缓存存储容器.
* @param identify 缓存标识
* @param converter 字段值类型与 String 的转换器.
* @param <V> 字段值类型.
* @return 返回新的映射表缓存存储容器.
* @throws GetCacheStoreException 当 Factory 无法返回 CacheStore 对象时抛出, 需说明失败原因.
*/
<V> MapCacheStore<V> newMapCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException;
/**
* 当前是否可以创建 {@link CacheStore}
*
* <p> 如果返回 true, 将会使用该 Factory.
* 如果返回 false 或抛出异常, 将不会通过该 Factory 创建 CacheStore,
*
* <p> 除非模块能保证 Factory 正常情况下一定能提供 CacheStore 对象,
* 否则请不要尝试永远返回 true 来向应用保证 Factory 一定能创建 CacheStore, 保持 Factory 的有效性,
* 一旦后续创建 CacheStore 时发生异常, 将视为无法创建.
* @return 如果可以, 返回 true.
*/
boolean canGetCacheStore();
}
@@ -0,0 +1,37 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache;
/**
* 缓存容器类型.
* @author LamGC
*/
public enum CacheStoreSource {
/**
* 内存存储(速度最快).
*/
MEMORY,
/**
* 本地存储(单机存储).
*/
LOCAL,
/**
* 远端存储(例如网络, 可多机读写且与单机无关).
*/
REMOTE
}
@@ -0,0 +1,94 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache;
import java.util.Collection;
/**
* 集合型缓存存储容器.
* @param <E> 元素类型.
* @param <C> 集合类型.
* @see Collection
* @author LamGC
*/
public interface CollectionCacheStore<E, C extends Collection<E>> extends CacheStore<C> {
/**
* 为缓存项添加一个元素.
* 当缓存项不存在时, 将会创建一个新的缓存项.
* @param key 缓存项键名.
* @param element 待添加的元素.
* @return 如果成功返回 true.
* @throws NullPointerException 当 key 或 element 为 null 时抛出; 本方法不允许存储 null 值, 因为 null 代表"没有/不存在".
*/
boolean addElement(CacheKey key, E element);
/**
* 为缓存项添加一组元素.
* 当缓存项不存在时, 将会创建一个新的缓存项.
* @param key 缓存项键名.
* @param elements 欲添加的元素集合.
* @return 如果成功添加, 返回 true, 如果无法添加(例如缓存项 List/Set 长度限制), 返回 false.
* @throws NullPointerException 当 key 或 value 为 null 时抛出; 本方法不允许存储 null 值, 因为 null 代表"没有/不存在".
*/
boolean addElements(CacheKey key, Collection<E> elements);
/**
* 检查指定元素是否包含在指定缓存项中.
* @param key 待检查的缓存项键名.
* @param element 待查找的缓存值.
* @return 如果存在, 返回 true, 如果元素不存在, 或缓存项不存在, 返回 false.
* @throws NullPointerException 当 key 或 element 为 null 时抛出; 本方法不允许存储 null 值, 因为 null 代表"没有/不存在".
*/
boolean containsElement(CacheKey key, E element);
/**
* 检查指定缓存项是否为空.
* @param key 待检查的缓存项键名.
* @return 如果缓存项无元素, 返回 true, 等效于 {@code elementsLength(key) == 0}
* @throws NullPointerException 当 key 为 null 时抛出.
*/
boolean isEmpty(CacheKey key);
/**
* 获取指定缓存项中的元素数量.
* @param key 待获取元素的缓存项键名.
* @return 返回指定缓存项中的元素数量, 如果缓存项不存在, 返回 -1.
* @throws NullPointerException 当 key 为 null 时抛出.
*/
int elementsLength(CacheKey key);
/**
* 清空集合中的所有元素.
* @param key 欲清空集合的缓存项键名.
* @return 操作成功返回 true.
* @throws NullPointerException 当 key 为 null 时抛出.
*/
boolean clearCollection(CacheKey key);
/**
* 删除缓存项中指定的元素.
* <p>该方法与 {@link CacheStore#remove(CacheKey)} 不同, 该方法仅删除缓存项中的指定元素, 即使删除后缓存项中没有元素, 也不会删除缓存项.
* @param key 待操作的缓存项键名.
* @param element 欲删除的元素.
* @return 如果元素存在且删除成功, 返回 true.
* @throws NullPointerException 当 key 为 null 时抛出.
*/
boolean removeElement(CacheKey key, E element);
}
@@ -0,0 +1,57 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author LamGC
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Factory {
/**
* Cache 模块名称.
* @return 返回实现模块名称.
*/
String name();
/**
* CacheStore 优先级.
* <p>最终所使用的 CacheStoreFactory 将会根据其优先级进行选择.
* 当优先级高的 Factory 表示无法创建 CacheStore 时, 将会寻找比该 Factory 优先级较低的下一个 Factory 并尝试获取,
* 重复该过程直到找到能使用的 Factory, 或者使用缺省的 CacheStore-local.
*
* <p>注意: 即使优先级超过 {@linkplain FactoryPriority#PRIORITY_HIGHEST 10} 也会被视为 10,
* 同样的, 即使优先级低于 {@linkplain FactoryPriority#PRIORITY_LOWEST 0} 也会被视为 0.
* @return 返回优先级, 最低优先级为 0, 优先级越高, 越会优先选择, 除非无法使用.
*/
int priority() default FactoryPriority.PRIORITY_NORMAL;
/**
* CacheStore 存储源类型.
* <p> 可帮助 Core 根据需要选择对应的类型(比如部分低TTL临时缓存追求速度不强求持久化, 建立热点数据高频短时缓存等).
* @return 返回存储源类型.
*/
CacheStoreSource source() default CacheStoreSource.LOCAL;
}
@@ -0,0 +1,61 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache;
/**
* Factory 优先级常量.
* @author LamGC
*/
public final class FactoryPriority {
/**
* 最高优先级
* <p> 优先级数值: 10
*/
public final static int PRIORITY_HIGHEST = 10;
/**
* 较高优先级
* <p> 优先级数值: 8
*/
public final static int PRIORITY_HIGHER = 8;
/**
* 普通优先级
* <p> 优先级数值: 5
*/
public final static int PRIORITY_NORMAL = 5;
/**
* 较低优先级
* <p> 优先级数值: 3
*/
public final static int PRIORITY_LOWER = 3;
/**
* 最高优先级
* <p> 优先级数值: 0
*/
public final static int PRIORITY_LOWEST = 0;
/**
* 该类不允许实例化.
*/
private FactoryPriority() {}
}
@@ -0,0 +1,79 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache;
import java.util.List;
/**
* List 类型的缓存存储容器.
* <p>该存储容器内的 List 与 {@link List} 概念相同, 即"有序列表".
* @param <E> 值类型.
* @author LamGC
*/
public interface ListCacheStore<E> extends CollectionCacheStore<E, List<E>> {
/**
* 获取缓存项中的指定元素.
* @param key 欲取值的缓存项键名.
* @param index 元素索引, 从 0 开始.
* @return 如果缓存项存在, 且指定索引存在元素, 则返回元素,
* 如果缓存项不存在, 或索引超出范围(超出长度或低于 0)等导致获取失败, 返回 null.
* @throws NullPointerException 当 key 为 null 时抛出.
*/
E getElement(CacheKey key, int index);
/**
* 根据返回获取部分元素.
* @param key 欲取值的缓存项键值.
* @param index 起始元素索引, 从 0 开始.
* @param length 获取长度.
* @return 如果成功, 返回成功获取到的元素数量, 失败返回 null.
* <p>对于起始元素索引超出范围, 或获取长度与实际能获取的长度不同的情况, 不允许返回 null,
* 相反, 返回的 List 中的元素数量应反映实际所可获取的元素数量, 例如:
* <ul>
* <li>因 index 超出范围(包括 index 低于 0, 也是如此)而无法获取元素, 则返回无元素 List 对象;
* <li>如果从起始元素开始获取无法获取 length 数量的元素, 则直接返回包含已成功获取元素的 List 对象.
* </ul>
* 但需要注意的是: 获取异常不包括在上述范围, 因此如果在获取中出现错误导致获取失败, 应以失败返回 null.
* @throws NullPointerException 当 key 为 null 时抛出.
*/
List<E> getElementsByRange(CacheKey key, int index, int length);
/**
* 删除指定索引的元素.
*
* <p>该方法与 {@link CacheStore#remove(CacheKey)} 不同, 该方法仅删除缓存项中的指定元素, 即使删除后缓存项中没有元素, 也不会删除缓存项.
* @param key 待操作的缓存项键名.
* @param index 欲删除元素的索引, 从 0 开始.
* @return 如果元素存在且删除成功, 返回 true.
* @throws NullPointerException 当 key 为 null 时抛出.
*/
boolean removeElement(CacheKey key, int index);
/**
* 删除缓存项中指定的元素.
* <p>当 List 存在多个该元素时, 删除第一个匹配到的元素.
* <p>该方法与 {@link CacheStore#remove(CacheKey)} 不同, 该方法仅删除缓存项中的指定元素, 即使删除后缓存项中没有元素, 也不会删除缓存项.
* @param key 待操作的缓存项键名.
* @param element 欲删除的元素.
* @return 如果元素存在且删除成功, 返回 true.
* @throws NullPointerException 当 key 为 null 时抛出.
*/
@Override
boolean removeElement(CacheKey key, E element);
}
@@ -0,0 +1,129 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache;
import java.util.Map;
import java.util.Set;
/**
* Map 缓存存储容器.
* @author LamGC
*/
public interface MapCacheStore<V> extends CacheStore<Map<String, V>> {
/**
* 获取 Map 字段数量.
* @param key Map 缓存项的键名.
* @return 返回 Map 字段数量, 如果缓存项不存在或获取失败, 返回 -1.
* @throws NullPointerException 当 key 为 null 时抛出.
*/
int mapSize(CacheKey key);
/**
* 获取 Map 字段集合.
* @param key 待查询的 Map 缓存项的键名.
* @return 返回 Map 字段集合, 如果缓存项不存在, 返回 null.
* @throws NullPointerException 当 key 为 null 时抛出.
*/
Set<String> mapFieldSet(CacheKey key);
/**
* 获取 Map 字段值集合.
* @param key 待查询的 Map 缓存项的键名.
* @return 返回 Map 字段值集合, 如果缓存项不存在, 返回 null.
* @throws NullPointerException 当 key 为 null 时抛出.
*/
Set<V> mapValueSet(CacheKey key);
/**
* 将指定的值与此映射中的指定字段关联.
* @param key Map 缓存项的键名.
* @param field 字段名.
* @param value 字段值.
* @return 如果成功返回 true.
* @throws NullPointerException 当 key/field/value 为 null 时抛出, 缓存存储容器不允许出现 null 值.
*/
boolean put(CacheKey key, String field, V value);
/**
* 添加一组字段.
* @param key Map 缓存项的键名.
* @param map 待添加的 Map.
* @return 如果成功返回 true.
* @throws NullPointerException 当 key/map 为 null 时抛出, 缓存存储容器不允许出现 null 值.
*/
boolean putAll(CacheKey key, Map<? extends String, ? extends V> map);
/**
* 如果字段不存在, 则会将指定的值与此映射中的指定字段关联.
*
* <p>该方法与 {@link #put(CacheKey, String, Object)} 类似, 但如果字段存在, 将不会执行任何操作并以失败返回.
* @param key Map 缓存项的键名.
* @param field 字段名.
* @param value 字段值.
* @return 如果字段不存在且设置成功, 返回 true, 否则返回 false.
* @throws NullPointerException 当 key/field/value 为 null 时抛出, 缓存存储容器不允许出现 null 值.
*/
boolean putIfNotExist(CacheKey key, String field, V value);
/**
* 获取指定字段的字段值.
* @param key Map 缓存项的键名.
* @param field 字段名.
* @return 如果 Map 缓存项存在且字段存在, 返回字段的对应值.
* @throws NullPointerException 当 key/field 为 null 时抛出.
*/
V get(CacheKey key, String field);
/**
* 删除 Map 中的指定字段.
* @param key Map 缓存项的键名.
* @param field 待删除的字段名.
* @return 如果 Map 缓存项存在, 字段存在并且删除成功, 返回 true.
* @throws NullPointerException 当 key/field 为 null 时抛出.
*/
boolean removeField(CacheKey key, String field);
/**
* 检查 Map 中是否有指定字段.
* @param key Map 缓存项的键名.
* @param field 待检查的字段名.
* @return 如果 Map 缓存项存在且字段存在, 返回 true.
* @throws NullPointerException 当 key/field 为 null 时抛出.
*/
boolean containsField(CacheKey key, String field);
/**
* 检查 Map 是否为空(没有任何字段).
*
* <p>该方法等价于 {@code mapSize(key) == 0}
* @param key Map 缓存项的键名.
* @return 如果 Map 缓存项存在且为空, 返回 true.
* @throws NullPointerException 当 key 为 null 时抛出.
*/
boolean mapIsEmpty(CacheKey key);
/**
* 清空 Map 中的所有字段(并不会删除 Map 缓存项).
* @param key 待清空的 Map 缓存项键名.
* @return 如果存在且清空成功, 返回 true.
* @throws NullPointerException 当 key 为 null 时抛出.
*/
boolean clearMap(CacheKey key);
}
@@ -0,0 +1,37 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache;
import java.util.Set;
/**
* 无序集合的缓存存储容器.
* <p>其中, 元素是唯一的, 不会出现重复情况.
* @param <E> 值类型.
* @author LamGC
*/
public interface SetCacheStore<E> extends CollectionCacheStore<E, Set<E>> {
/*
* 说实话, SetCacheStore 的存在有点...奇怪, 或者可能就没有用,
* 因为根据 2 代对缓存存储的使用情况来看, 用到 Set 的地方根本就没有,
* 而且可能在某些方面实现起来也不是件容易事情.
* 所以 SetCacheStore 可能会废弃掉(最晚也会在正式版出来前),
* 或者有用的话才会为其设计相关方法.
* (比如偏向 Redis 的那套进行设计, 但是我希望不要出现偏向性, 为所有实现提供一个平衡的实现复杂度)
*/
}
@@ -0,0 +1,57 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache;
/**
* 单项存取的缓存存储容器.
* <p>该缓存存储容器在存储上, 一个键对应一个值, 不存在一对多的情况.
* @param <V> 值类型.
* @author LamGC
*/
public interface SingleCacheStore<V> extends CacheStore<V> {
/**
* 设置指定键为指定值.
* 如果缓存项不存在, 则新建缓存并存储, 如果存在, 则覆盖原缓存;
* <p>覆盖缓存相当于是"删除"该缓存并重新创建, "删除"意味着原缓存项的相关设置将会丢失(例如"过期时间").
* @param key 缓存项键名.
* @param value 缓存值.
* @return 如果成功返回 true.
* @throws NullPointerException 当 key 或 value 为 null 时抛出; 本方法不允许存储 null 值, 因为 null 代表"没有/不存在".
*/
boolean set(CacheKey key, V value);
/**
* 设置指定键为指定值.
* <p>该方法与 {@link #set(CacheKey, Object)} 类似, 但如果该 key 已经存在缓存, 则不执行 set 操作并返回 false.
* @param key 缓存项键名.
* @param value 缓存值.
* @return 如果成功返回 true, 当 key 已存在, 或设置失败时返回 false.
* @throws NullPointerException 当 key 或 value 为 null 时抛出; 本方法不允许存储 null 值, 因为 null 代表"没有/不存在".
*/
boolean setIfNotExist(CacheKey key, V value);
/**
* 获取缓存项值.
* @param key 欲取值的缓存项键名.
* @return 如果缓存项存在, 返回缓存项的值, 否则返回 null.
* @throws NullPointerException 当 key 为 null 时抛出.
*/
V get(CacheKey key);
}
@@ -0,0 +1,42 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.convert;
/**
* 转换器接口
* @param <S> 源类型
* @param <T> 目标类型
* @author LamGC
*/
public interface Converter<S, T> {
/**
* 将源类型对象转换成目标类型.
* @param source 源类型对象.
* @return 对应的目标类型对象.
*/
T to(S source);
/**
* 从目标类型对象转换回源类型.
* @param target 目标类型对象.
* @return 对应的源类型对象.
*/
S from(T target);
}
@@ -0,0 +1,27 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.convert;
/**
* 某类型到字符串的转换器接口.
* @param <T> 源类型.
* @author LamGC
*/
public interface StringConverter<T> extends Converter<T, String> {
}
@@ -0,0 +1,35 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.convert;
/**
* 字符串原样转换的转换器.
* 不进行任何处理.
* @author LamGC
*/
public class StringToStringConverter implements StringConverter<String> {
@Override
public String to(String source) {
return source;
}
@Override
public String from(String target) {
return target;
}
}
@@ -0,0 +1,34 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.exception;
/**
* 获取 CacheStore 异常.
* <p>当无法获取 CacheStore 时抛出.
* @author LamGC
*/
public class GetCacheStoreException extends RuntimeException {
public GetCacheStoreException(String message) {
super(message);
}
public GetCacheStoreException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -0,0 +1,63 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache;
import org.junit.Assert;
import org.junit.Test;
/**
* @see CacheKey
*/
public class CacheKeyTest {
@Test
public void hashCodeAndEqualsTest() {
CacheKey key = new CacheKey("test");
Assert.assertEquals(key, key);
Assert.assertNotEquals(key, null);
Assert.assertNotEquals(key, new Object());
Assert.assertEquals(new CacheKey("test", "key01"), new CacheKey("test", "key01"));
Assert.assertEquals(new CacheKey("test", "key01").hashCode(), new CacheKey("test", "key01").hashCode());
Assert.assertNotEquals(new CacheKey("test", "key01"), new CacheKey("test", "key00"));
Assert.assertNotEquals(new CacheKey("test", "key01").hashCode(), new CacheKey("test", "key00").hashCode());
Assert.assertEquals(new CacheKey("test", "key01").toString(), "test.key01");
}
@Test
public void buildTest() {
Assert.assertThrows(NullPointerException.class, () -> new CacheKey(null));
Assert.assertThrows(NullPointerException.class, () -> new CacheKey(null, new String[0]));
Assert.assertThrows(IllegalArgumentException.class, () -> new CacheKey(new String[0]));
final String[] keys = new String[] {"test", "key01"};
Assert.assertArrayEquals(new CacheKey(keys).getKeyArray(), keys);
}
@Test
public void joinTest() {
Assert.assertEquals("test.key01", new CacheKey("test", "key01").join("."));
Assert.assertEquals("test:key01", new CacheKey("test", "key01").join(":"));
}
@Test
public void nullValueCheckTest() {
CacheKey test = new CacheKey("test", (String[]) null);
Assert.assertEquals("test", test.toString());
Assert.assertThrows(NullPointerException.class, () -> new CacheKey("test", (String) null));
}
}
@@ -0,0 +1,38 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.convert;
import org.junit.Assert;
import org.junit.Test;
/**
* @see StringToStringConverter
*/
public class StringToStringConverterTest {
private final Converter<String, String> stringConverter = new StringToStringConverter();
private final static String TEST_CONTENT = "test";
@Test
public void convertTest() {
Assert.assertEquals(TEST_CONTENT, stringConverter.to(TEST_CONTENT));
Assert.assertEquals(TEST_CONTENT, stringConverter.from(TEST_CONTENT));
}
}
@@ -0,0 +1,39 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.exception;
import org.junit.Assert;
import org.junit.Test;
public class GetCacheStoreExceptionTest {
@Test
public void messageCheck() {
final String message = "uncaught exception";
Assert.assertEquals(message, new GetCacheStoreException(message).getMessage());
}
@Test
public void causeCheck() {
final String message = "uncaught exception";
final Throwable cause = new IllegalStateException();
Assert.assertEquals(cause, new GetCacheStoreException(message, cause).getCause());
}
}
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (C) 2021 LamGC
~
~ ContentGrabbingJi is free software: you can redistribute it and/or modify
~ it under the terms of the GNU Affero General Public License as
~ published by the Free Software Foundation, either version 3 of the
~ License.
~
~ ContentGrabbingJi is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU Affero General Public License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ContentGrabbingJi</artifactId>
<groupId>net.lamgc</groupId>
<version>3.0.0-alpha-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ContentGrabbingJi-CacheStore-local</artifactId>
<dependencies>
<dependency>
<groupId>net.lamgc</groupId>
<artifactId>ContentGrabbingJi-CacheStore-api</artifactId>
<version>3.0.0-alpha-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,122 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.local;
import com.google.common.base.Throwables;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
/**
* 定时清理机制.
* <p>定时通知已实现 {@link Cleanable} 接口的对象进行清理.
* @see Cleanable
* @author LamGC
*/
public class AutoCleanTimer implements Runnable {
private final static Set<WeakReference<Cleanable>> CLEANABLE_STORE_SET = new CopyOnWriteArraySet<>();
private final static ScheduledExecutorService SCHEDULED_EXECUTOR =
new ScheduledThreadPoolExecutor(1,
new ThreadFactoryBuilder()
.setDaemon(true)
.setNameFormat("Thread-AutoClean-%d")
.build()
);
private final static Logger log = LoggerFactory.getLogger(AutoCleanTimer.class);
private final static AtomicReference<ReferenceQueue<Cleanable>> REFERENCE_QUEUE = new AtomicReference<>(null);
static {
SCHEDULED_EXECUTOR.scheduleAtFixedRate(new AutoCleanTimer(), 100L, 100L, TimeUnit.MILLISECONDS);
Runtime.getRuntime().addShutdownHook(new Thread(SCHEDULED_EXECUTOR::shutdownNow, "ShutdownThread-AutoClean"));
}
/**
* 增加需要定时执行清理的缓存库.
* @param store 已实现Cleanable的对象
*/
public static void add(Cleanable store) {
CLEANABLE_STORE_SET.add(new WeakReference<>(store, REFERENCE_QUEUE.get()));
}
/**
* 移除对指定 Cleanable 的轮询.
* @param store 欲停止轮询的 Cleanable 对象.
*/
public static void remove(final Cleanable store) {
CLEANABLE_STORE_SET.removeIf(cleanableReference -> cleanableReference.get() == store);
}
/**
* 获取当前被轮询的 Cleanable 数量.
* @return 返回轮询的 Cleanable 数量.
*/
public static int size() {
return CLEANABLE_STORE_SET.size();
}
/**
* 设置虚引用回收队列, 以检查虚引用对象回收状况.
* <p> 本方法用于诊断 AutoCleanTimer 对虚引用对象的处理情况, 一般情况下无需使用.
* @param queue 引用队列.
*/
public static void setWeakReferenceQueue(ReferenceQueue<Cleanable> queue) {
REFERENCE_QUEUE.set(queue);
}
private AutoCleanTimer() {}
private final Set<WeakReference<Cleanable>> toBeCleanReference = new HashSet<>();
@Override
public void run() {
if (CLEANABLE_STORE_SET.size() == 0) {
return;
}
for (WeakReference<Cleanable> reference : CLEANABLE_STORE_SET) {
Cleanable store = reference.get();
if (reference.isEnqueued() || store == null) {
// 由于 COW ArraySet 的 Iterator 不支持 remove 操作,
// 所以先收集起来, 等完成所有清理工作后统一删除引用.
toBeCleanReference.add(reference);
continue;
}
try {
store.clean();
} catch (Exception e) {
log.error("{} 执行清理动作时发生异常:\n{}", store.toString(), Throwables.getStackTraceAsString(e));
}
}
if (toBeCleanReference.size() != 0) {
CLEANABLE_STORE_SET.removeAll(toBeCleanReference);
toBeCleanReference.clear();
}
}
}
@@ -0,0 +1,33 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.local;
/**
* 可清理接口, 实现该接口代表该类具有清理动作.
* @author LamGC
*/
public interface Cleanable {
/**
* 该方法需要CacheStore完成对过期Entry的清除.
* @return 返回已清理数量.
* @throws Exception 即使该方法抛出异常, 也不会影响后续情况.
*/
long clean() throws Exception;
}
@@ -0,0 +1,95 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.local;
import net.lamgc.cgj.bot.cache.CacheKey;
import net.lamgc.cgj.bot.cache.ListCacheStore;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* 基于 {@link CopyOnWriteArrayList} 的有序列表缓存存储容器.
* @param <E> 元素类型.
* @author LamGC
*/
public class CopyOnWriteArrayListCacheStore<E>
extends LocalCollectionCacheStore<E, List<E>>
implements ListCacheStore<E>
{
@Override
public E getElement(CacheKey key, int index) {
List<E> itemCollection = getCacheItemCollection(key, false);
try {
return itemCollection == null ? null : itemCollection.get(index);
} catch (IndexOutOfBoundsException e) {
return null;
}
}
@Override
public List<E> getElementsByRange(CacheKey key, int index, int length) {
int listLength = elementsLength(key);
if (listLength == -1) {
return null;
}
List<E> itemCollection = getCacheItemCollection(key, false);
List<E> result = new ArrayList<>();
try {
ListIterator<E> iterator = itemCollection.listIterator(index);
for (int i = 0; i < length && iterator.hasNext(); i++) {
result.add(iterator.next());
}
} catch (IndexOutOfBoundsException ignored) {
// 正常情况来讲, 该 try-catch 块只有 listIterator 会抛出 IndexOutOfBoundsException,
// 而一旦抛出 IndexOutOfBoundsException, 就代表 index 溢出了, try 块后面代码没有继续执行,
// 既然抛出异常时, result 并没有添加任何元素, 为何要再 new 一个 List 浪费内存呢? :D
}
return result;
}
@Override
public boolean removeElement(CacheKey key, int index) {
List<E> itemCollection = getCacheItemCollection(key, false);
if (itemCollection != null) {
try {
itemCollection.remove(index);
return true;
} catch (ArrayIndexOutOfBoundsException e) {
return false;
}
}
return false;
}
@Override
protected List<E> getCacheItemCollection(CacheKey key, boolean create) {
Objects.requireNonNull(key);
Map<String, CacheItem<List<E>>> cacheMap = getCacheMap();
if (!cacheMap.containsKey(key.toString())) {
if (create) {
cacheMap.put(key.toString(), new CacheItem<>(new CopyOnWriteArrayList<>()));
} else {
return null;
}
}
return cacheMap.get(key.toString()).getValue();
}
}
@@ -0,0 +1,163 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.local;
import net.lamgc.cgj.bot.cache.CacheKey;
import net.lamgc.cgj.bot.cache.CacheStore;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
/**
* 基于 {@link Hashtable} 的缓存存储容器.
* @param <V> 值类型.
* @author LamGC
* @see net.lamgc.cgj.bot.cache.CacheStore
* @see Hashtable
*/
public abstract class HashCacheStore<V> implements CacheStore<V>, Cleanable {
private final Map<String, CacheItem<V>> cacheMap = new Hashtable<>();
/**
* 获取内部 Map 对象.
* 仅供其他子类使用.
* @return 返回存储缓存项的 Map.
*/
protected Map<String, CacheItem<V>> getCacheMap() {
return cacheMap;
}
@Override
public boolean setTimeToLive(CacheKey key, long ttl) {
if (!exists(key)) {
return false;
}
CacheItem<V> item = cacheMap.get(key.toString());
item.setExpireDate(ttl < 0 ? null : new Date(System.currentTimeMillis() + ttl));
return true;
}
@Override
public long getTimeToLive(CacheKey key) {
if (!exists(key)) {
return -1;
}
CacheItem<V> item = cacheMap.get(key.toString());
Date expireDate = item.getExpireDate();
if (expireDate != null) {
return expireDate.getTime() - System.currentTimeMillis();
}
return -1;
}
@Override
public long size() {
return cacheMap.size();
}
@Override
public boolean clear() {
cacheMap.clear();
return true;
}
@Override
public boolean exists(CacheKey key) {
if (!cacheMap.containsKey(key.toString())) {
return false;
}
CacheItem<V> item = cacheMap.get(key.toString());
// 在检查其过期情况后根据情况进行清理, 减轻主动清理机制的负担.
if (item.isExpire(new Date())) {
remove(key);
return false;
}
return true;
}
@Override
public boolean remove(CacheKey key) {
// 根据 Collection 说明, 删除时 key 存在映射就会返回, 只要返回 null 就代表没有.
return cacheMap.remove(key.toString()) != null;
}
@Override
public Set<String> keySet() {
return Collections.unmodifiableSet(cacheMap.keySet());
}
@Override
public long clean() {
Map<String, CacheItem<V>> cacheMap = getCacheMap();
Date currentDate = new Date();
AtomicLong cleanCount = new AtomicLong(0);
cacheMap.keySet().removeIf(key -> {
CacheItem<V> item = cacheMap.get(key);
if (item.isExpire(currentDate)) {
cleanCount.incrementAndGet();
return true;
}
return false;
});
return cleanCount.get();
}
/**
* 缓存项.
* @author LamGC
*/
protected final static class CacheItem<V> {
private final V value;
private Date expireDate;
CacheItem(V value) {
this(value, null);
}
CacheItem(V value, Date expireDate) {
this.value = value;
this.expireDate = expireDate;
}
public V getValue() {
return value;
}
public void setExpireDate(Date expireDate) {
this.expireDate = expireDate;
}
public Date getExpireDate() {
return expireDate;
}
/**
* 检查缓存项是否过期.
* @param date 当前时间.
* @return 如果已设置过期时间且早于提供的Date, 则该缓存项过期, 返回 true.
* @throws NullPointerException 当 date 传入 null 时抛出.
*/
public boolean isExpire(Date date) {
Date expireDate = getExpireDate();
return expireDate != null && expireDate.before(Objects.requireNonNull(date));
}
}
}
@@ -0,0 +1,119 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.local;
import net.lamgc.cgj.bot.cache.CacheKey;
import net.lamgc.cgj.bot.cache.MapCacheStore;
import java.util.*;
import java.util.function.Function;
/**
*
* @param <V>
* @see net.lamgc.cgj.bot.cache.CacheStore
* @see net.lamgc.cgj.bot.cache.MapCacheStore
* @author LamGC
*/
public class HashMapCacheStore<V> extends HashCacheStore<Map<String, V>> implements MapCacheStore<V> {
@Override
public int mapSize(CacheKey key) {
return getMap(key, false, Map::size, -1);
}
@Override
public Set<String> mapFieldSet(CacheKey key) {
return getMap(key, false, map -> Collections.unmodifiableSet(map.keySet()), null);
}
@Override
public Set<V> mapValueSet(CacheKey key) {
return getMap(key, false, map -> new HashSet<>(map.values()), null);
}
@Override
public boolean put(CacheKey key, String field, V value) {
return getMap(key, true, map -> {
map.put(Objects.requireNonNull(field), Objects.requireNonNull(value));
return true;
}, false);
}
@Override
public boolean putAll(CacheKey key, Map<? extends String, ? extends V> map) {
return getMap(key, true, keyMap -> {
keyMap.putAll(Objects.requireNonNull(map));
return true;
}, false);
}
@Override
public boolean putIfNotExist(CacheKey key, String field, V value) {
return getMap(key, true, map -> {
if (map.containsKey(Objects.requireNonNull(field))) {
return false;
}
map.put(Objects.requireNonNull(field), Objects.requireNonNull(value));
return true;
}, false);
}
@Override
public V get(CacheKey key, String field) {
return getMap(key, false, map -> map.get(Objects.requireNonNull(field)), null);
}
@Override
public boolean removeField(CacheKey key, String field) {
return getMap(key, false, map -> map.remove(Objects.requireNonNull(field)) != null, false);
}
@Override
public boolean containsField(CacheKey key, String field) {
return getMap(key, false, map -> map.containsKey(Objects.requireNonNull(field)), false);
}
@Override
public boolean mapIsEmpty(CacheKey key) {
return getMap(key, false, Map::isEmpty, false);
}
@Override
public boolean clearMap(CacheKey key) {
return getMap(key, false, map -> {
map.clear();
return true;
}, false);
}
private <R> R getMap(CacheKey key, boolean create, Function<Map<String, V>, R> notNull, R isNull) {
Objects.requireNonNull(key);
String keyString = key.toString();
Map<String, CacheItem<Map<String, V>>> cacheMap = getCacheMap();
if (!cacheMap.containsKey(keyString)) {
if (create) {
cacheMap.put(keyString, new CacheItem<>(new Hashtable<>()));
} else {
return isNull;
}
}
return notNull.apply(cacheMap.get(keyString).getValue());
}
}
@@ -0,0 +1,47 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.local;
import net.lamgc.cgj.bot.cache.CacheKey;
import net.lamgc.cgj.bot.cache.SetCacheStore;
import java.util.*;
/**
*
* @param <E> 元素类型.
* @author LamGC
*/
public class HashSetCacheStore<E> extends LocalCollectionCacheStore<E, Set<E>> implements SetCacheStore<E> {
@Override
protected Set<E> getCacheItemCollection(CacheKey key, boolean create) {
Objects.requireNonNull(key);
String keyString = key.toString();
Map<String, CacheItem<Set<E>>> cacheMap = getCacheMap();
if (!cacheMap.containsKey(keyString)) {
if (create) {
cacheMap.put(keyString, new CacheItem<>(new HashSet<>()));
} else {
return null;
}
}
return cacheMap.get(keyString).getValue();
}
}
@@ -0,0 +1,54 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.local;
import net.lamgc.cgj.bot.cache.CacheKey;
import net.lamgc.cgj.bot.cache.SingleCacheStore;
import java.util.Objects;
/**
* 基于 {@link java.util.Hashtable} 的 Map 缓存存储容器.
* @param <V> 值类型.
* @author LamGC
*/
public class HashSingleCacheStore<V> extends HashCacheStore<V> implements SingleCacheStore<V> {
@Override
public boolean set(CacheKey key, V value) {
getCacheMap().put(Objects.requireNonNull(key).toString(), new CacheItem<>(Objects.requireNonNull(value)));
return true;
}
@Override
public boolean setIfNotExist(CacheKey key, V value) {
if (exists(key)) {
return false;
}
return set(key, value);
}
@Override
public V get(CacheKey key) {
if (!exists(key)) {
return null;
}
return getCacheMap().get(key.toString()).getValue();
}
}
@@ -0,0 +1,70 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.local;
import net.lamgc.cgj.bot.cache.*;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import java.io.File;
/**
* 本地缓存存储容器工厂.
* 最快速但又是最占内存的方法, 适用于远端缓存失效, 或无远端缓存的情况下使用.
* 最简单的缓存实现, 无持久化功能.
* @author LamGC
*/
@Factory(name = "Local-Memory", priority = FactoryPriority.PRIORITY_LOWEST, source = CacheStoreSource.MEMORY)
public class LocalCacheStoreFactory implements CacheStoreFactory {
@Override
public void initial(File dataDirectory) {
// 不需要做任何事情, 除非需要做持久化.
}
@Override
public <V> SingleCacheStore<V> newSingleCacheStore(String identify, StringConverter<V> converter) {
return new HashSingleCacheStore<>();
}
@Override
public <V> ListCacheStore<V> newListCacheStore(String identify, StringConverter<V> converter) {
return new CopyOnWriteArrayListCacheStore<>();
}
@Override
public <V> SetCacheStore<V> newSetCacheStore(String identify, StringConverter<V> converter) {
return new HashSetCacheStore<>();
}
@Override
public <V> MapCacheStore<V> newMapCacheStore(String identify, StringConverter<V> converter) {
return new HashMapCacheStore<>();
}
/**
* 内存使用阀值.
* <p>当内存使用到了指定百分比时, 将禁止创建 CacheStore.
*/
private final static double MEMORY_USAGE_THRESHOLD = 85;
@Override
public boolean canGetCacheStore() {
Runtime runtime = Runtime.getRuntime();
double memoryUsedPercentage = (double) runtime.totalMemory() / runtime.maxMemory();
return memoryUsedPercentage < MEMORY_USAGE_THRESHOLD;
}
}
@@ -0,0 +1,111 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.local;
import net.lamgc.cgj.bot.cache.CacheKey;
import net.lamgc.cgj.bot.cache.CollectionCacheStore;
import java.util.Collection;
import java.util.Objects;
/**
* 本地集合缓存存储容器.
* @param <E> 元素类型
* @author LamGC
* @see net.lamgc.cgj.bot.cache.CacheStore
* @see net.lamgc.cgj.bot.cache.CollectionCacheStore
*/
public abstract class LocalCollectionCacheStore<E, C extends Collection<E>>
extends HashCacheStore<C>
implements CollectionCacheStore<E, C> {
/**
* 获取缓存项集合对象.
* @param key 缓存项键名
* @param create 如果不存在, 是否创建.
* @return 如果不存在且 create 为 false, 或添加失败, 返回 false, 添加成功返回 true.
*/
protected abstract C getCacheItemCollection(CacheKey key, boolean create);
@Override
public boolean addElement(CacheKey key, E element) {
Objects.requireNonNull(key);
Objects.requireNonNull(element);
Collection<E> itemCollection = getCacheItemCollection(key, true);
return itemCollection.add(element);
}
@Override
public boolean addElements(CacheKey key, Collection<E> elements) {
Objects.requireNonNull(key);
Objects.requireNonNull(elements);
Collection<E> itemCollection = getCacheItemCollection(key, true);
return itemCollection.addAll(elements);
}
@Override
public boolean containsElement(CacheKey key, E value) {
Objects.requireNonNull(key);
Objects.requireNonNull(value);
Collection<E> itemCollection = getCacheItemCollection(key, false);
if (itemCollection == null) {
return false;
}
return itemCollection.contains(value);
}
@Override
public boolean isEmpty(CacheKey key) {
Collection<E> itemCollection = getCacheItemCollection(Objects.requireNonNull(key), false);
if (itemCollection == null) {
return false;
}
return itemCollection.isEmpty();
}
@Override
public int elementsLength(CacheKey key) {
Collection<E> itemCollection = getCacheItemCollection(Objects.requireNonNull(key), false);
if (itemCollection == null) {
return -1;
}
return itemCollection.size();
}
@Override
public boolean clearCollection(CacheKey key) {
Collection<E> itemCollection = getCacheItemCollection(Objects.requireNonNull(key), false);
if (itemCollection == null) {
return false;
}
itemCollection.clear();
return true;
}
@Override
public boolean removeElement(CacheKey key, E element) {
Objects.requireNonNull(key);
Objects.requireNonNull(element);
Collection<E> itemCollection = getCacheItemCollection(key, false);
if (itemCollection == null) {
return false;
}
return itemCollection.remove(element);
}
}
@@ -0,0 +1,35 @@
#
# Copyright (C) 2021 LamGC
#
# ContentGrabbingJi is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License.
#
# ContentGrabbingJi is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
#
# Copyright (C) 2020 LamGC
#
# ContentGrabbingJi is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# ContentGrabbingJi is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
net.lamgc.cgj.bot.cache.local.LocalCacheStoreFactory
@@ -0,0 +1,91 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.local;
import net.lamgc.cgj.bot.cache.CacheKey;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import java.lang.ref.ReferenceQueue;
/**
* @see AutoCleanTimer
*/
public class AutoCleanTimerTest {
@BeforeClass
public static void before() throws ClassNotFoundException, InterruptedException {
Class.forName(AutoCleanTimer.class.getName(), true, ClassLoader.getSystemClassLoader());
Thread.sleep(150L);
}
@Test
public void addTest() throws InterruptedException {
HashSingleCacheStore<String> cacheStore = new HashSingleCacheStore<>();
AutoCleanTimer.add(cacheStore);
Thread.sleep(300L);
final CacheKey persistenceKey = new CacheKey("persistenceKey");
final CacheKey expireKey = new CacheKey("expireKey");
final String value = "testValue";
// 过期键与持久键
cacheStore.set(persistenceKey, value);
cacheStore.set(expireKey, value);
cacheStore.setTimeToLive(expireKey, 50);
Thread.sleep(1000L);
Assert.assertTrue(cacheStore.exists(persistenceKey));
Assert.assertFalse(cacheStore.exists(expireKey));
}
@Test
public void weakReferenceCleanTest() throws InterruptedException {
ReferenceQueue<Cleanable> referenceQueue = new ReferenceQueue<>();
AutoCleanTimer.setWeakReferenceQueue(referenceQueue);
AutoCleanTimer.add(new HashSingleCacheStore<>());
System.gc();
Assert.assertNotNull(referenceQueue.remove(100L));
System.gc();
Thread.sleep(300L);
Assert.assertEquals(0, AutoCleanTimer.size());
}
@Test
public void methodExceptionThrowTest() throws InterruptedException {
class ThrowExceptionCleanable implements Cleanable {
private boolean throed;
@Override
public long clean() throws Exception {
if (!throed) {
throed = true;
throw new Exception();
}
return 0;
}
}
ThrowExceptionCleanable cleanable = new ThrowExceptionCleanable();
AutoCleanTimer.add(cleanable);
Thread.sleep(300L);
AutoCleanTimer.remove(cleanable);
}
}
@@ -0,0 +1,139 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.local;
import com.google.common.collect.Lists;
import net.lamgc.cgj.bot.cache.CacheKey;
import net.lamgc.cgj.bot.cache.ListCacheStore;
import org.junit.Assert;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* @see CopyOnWriteArrayListCacheStore
* @see LocalCollectionCacheStore
*/
public class CopyOnWriteArrayListCacheStoreTest {
@Test
public void nullThrowTest() {
final ListCacheStore<String> cacheStore = new CopyOnWriteArrayListCacheStore<>();
final CacheKey key = new CacheKey("testKey");
// LocalCollectionCacheStore
Assert.assertThrows(NullPointerException.class, () -> cacheStore.addElement(null, "testValue"));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.addElement(key, null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.addElements(null, new ArrayList<>()));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.addElements(key, null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.elementsLength(null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.containsElement(null, "testValue"));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.containsElement(key, null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.isEmpty(null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.clearCollection(null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.removeElement(null, "testValue"));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.removeElement(key, null));
// CopyOnWriteArrayListCacheStore
Assert.assertThrows(NullPointerException.class, () -> cacheStore.getElement(null, 0));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.getElementsByRange(null, 0, 0));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.removeElement(null, 0));
}
@Test
public void notExistCacheTest() {
final ListCacheStore<String> cacheStore = new CopyOnWriteArrayListCacheStore<>();
final CacheKey key = new CacheKey("testKey");
Assert.assertFalse(cacheStore.clearCollection(key));
Assert.assertFalse(cacheStore.isEmpty(key));
Assert.assertEquals(-1, cacheStore.elementsLength(key));
Assert.assertFalse(cacheStore.containsElement(key, "testValue"));
Assert.assertFalse(cacheStore.removeElement(key, "testValue"));
}
@Test
public void addAndGetTest() {
final ListCacheStore<Integer> cacheStore = new CopyOnWriteArrayListCacheStore<>();
final CacheKey key = new CacheKey("testKey");
List<Integer> numbers = Lists.newArrayList(1, 2, 3, 4, 5, 6, 7, 8, 9);
// getElement/getElementsByRange Cache不存在测试
Assert.assertNull(cacheStore.getElement(key, 0));
Assert.assertNull(cacheStore.getElementsByRange(key, 0, 1));
// addElement/getElement 正常情况测试
Assert.assertTrue("addElement operation failed!", cacheStore.addElement(key, 0));
Assert.assertEquals(new Integer(0), cacheStore.getElement(key, 0));
// 超出范围的 null 测试
Assert.assertNull(cacheStore.getElement(key, cacheStore.elementsLength(key)));
// addElements/getElementsByRange 正常情况测试
Assert.assertTrue("addElements operation failed!", cacheStore.addElements(key, numbers));
Assert.assertEquals(Lists.newArrayList(0, 1, 2), cacheStore.getElementsByRange(key, 0, 3));
// 不足长度的 getElementsByRange
Assert.assertEquals(Lists.newArrayList(7, 8, 9), cacheStore.getElementsByRange(key, 7, 8));
// 超出索引的 getElementsByRange
List<Integer> result = cacheStore.getElementsByRange(key, cacheStore.elementsLength(key) + 1, 8);
Assert.assertNotNull("getElementsByRange returned null if index is out of range", result);
Assert.assertEquals("getElementsByRange returned a non empty list when the index was out of range",
0, result.size());
// 不足长度的 getElementsByRange
Assert.assertEquals(Lists.newArrayList(), cacheStore.getElementsByRange(key, cacheStore.elementsLength(key), 0));
}
@Test
public void removeElementTest() {
// removeElement(String, E) / removeElement(String, int)
final ListCacheStore<String> cacheStore = new CopyOnWriteArrayListCacheStore<>();
final CacheKey key = new CacheKey("testKey");
Random random = new Random();
List<String> numbers = Lists.newArrayList("1", "2", "3", "4", "5", "6", "7", "8", "9");
// 删除不存在 Cache 返回 false
Assert.assertFalse(cacheStore.removeElement(key, 0));
Assert.assertTrue("addElements operation failed!", cacheStore.addElements(key, numbers));
int removeIndex = random.nextInt(cacheStore.elementsLength(key));
numbers.remove(removeIndex);
Assert.assertTrue("removeElement operation failed!", cacheStore.removeElement(key, removeIndex));
Assert.assertEquals(numbers, cacheStore.getElementsByRange(key, 0, cacheStore.elementsLength(key)));
String removeTarget = cacheStore.getElement(key, random.nextInt(cacheStore.elementsLength(key)));
Assert.assertNotNull(removeTarget);
Assert.assertTrue(cacheStore.containsElement(key, removeTarget));
numbers.remove(removeTarget);
Assert.assertTrue("removeElement operation failed!", cacheStore.removeElement(key, removeTarget));
Assert.assertEquals(numbers, cacheStore.getElementsByRange(key, 0, cacheStore.elementsLength(key)));
Assert.assertTrue("clearCollection operation failed!", cacheStore.clearCollection(key));
Assert.assertTrue(cacheStore.exists(key));
Assert.assertEquals(0, cacheStore.elementsLength(key));
Assert.assertTrue(cacheStore.isEmpty(key));
// 删除不存在元素返回 false
Assert.assertFalse(cacheStore.removeElement(key, cacheStore.elementsLength(key)));
}
}
@@ -0,0 +1,132 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.local;
import net.lamgc.cgj.bot.cache.CacheKey;
import net.lamgc.cgj.bot.cache.MapCacheStore;
import org.junit.Assert;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
/**
* @see HashMapCacheStore
*/
public class HashMapCacheStoreTest {
@Test
public void nullThrowTest() {
final MapCacheStore<String> cacheStore = new HashMapCacheStore<>();
final CacheKey key = new CacheKey("testKey");
Assert.assertThrows(NullPointerException.class, () -> cacheStore.mapSize(null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.mapFieldSet(null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.mapValueSet(null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.put(null, "field", "value"));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.put(key, null, "value"));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.put(key, "field", null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.putAll(null, new HashMap<>()));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.putAll(key, null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.putIfNotExist(null, "field", "value"));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.putIfNotExist(key, null, "value"));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.putIfNotExist(key, "field", null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.get(key, null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.get(null, "field"));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.removeField(key, null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.removeField(null, "field"));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.mapIsEmpty(null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.clearMap(null));
}
@Test
public void keyNotExistTest() {
final MapCacheStore<String> cacheStore = new HashMapCacheStore<>();
final CacheKey key = new CacheKey("testKey");
Assert.assertEquals(-1, cacheStore.mapSize(key));
Assert.assertFalse(cacheStore.mapIsEmpty(key));
Assert.assertFalse(cacheStore.clearMap(key));
Assert.assertFalse(cacheStore.containsField(key, "Field"));
Assert.assertFalse(cacheStore.removeField(key, "Field"));
Assert.assertNull(cacheStore.get(key, "Field"));
Assert.assertTrue(cacheStore.put(key, "Field", "value"));
Assert.assertTrue("clearMap operation failed!", cacheStore.remove(key));
Assert.assertTrue(cacheStore.putAll(key, new HashMap<>()));
Assert.assertTrue("clearMap operation failed!", cacheStore.remove(key));
Assert.assertTrue(cacheStore.putIfNotExist(key, "Field", "value"));
}
@Test
public void putAndGetTest() {
final MapCacheStore<String> cacheStore = new HashMapCacheStore<>();
final CacheKey key = new CacheKey("testKey");
final Map<String, String> expectedMap = new HashMap<>();
expectedMap.put("test01", "testValue01");
expectedMap.put("test02", "testValue02");
expectedMap.put("test03", "testValue03");
expectedMap.put("test04", "testValue04");
expectedMap.put("test05", "testValue05");
expectedMap.put("test06", "testValue06");
// put/get, mapIsEmpty, containsField
Assert.assertTrue("put operation failed!", cacheStore.put(key, "test00", "testValue00"));
Assert.assertTrue(cacheStore.containsField(key, "test00"));
Assert.assertEquals("testValue00", cacheStore.get(key, "test00"));
Assert.assertTrue("removeField operation failed!", cacheStore.removeField(key, "test00"));
// putIfNotExist
Assert.assertTrue(cacheStore.putIfNotExist(key, "test00", "testValue00"));
Assert.assertFalse(cacheStore.putIfNotExist(key, "test00", "testValue00"));
Assert.assertTrue("clearMap operation failed!", cacheStore.clearMap(key));
// putAll
Assert.assertTrue(cacheStore.putAll(key, expectedMap));
Assert.assertTrue(expectedMap.keySet().containsAll(cacheStore.mapFieldSet(key)));
Assert.assertTrue(expectedMap.values().containsAll(cacheStore.mapValueSet(key)));
}
@Test
public void fieldChangeTest() {
final MapCacheStore<String> cacheStore = new HashMapCacheStore<>();
final CacheKey key = new CacheKey("testKey");
final Map<String, String> expectedMap = new HashMap<>();
expectedMap.put("test01", "testValue01");
expectedMap.put("test02", "testValue02");
expectedMap.put("test03", "testValue03");
expectedMap.put("test04", "testValue04");
expectedMap.put("test05", "testValue05");
expectedMap.put("test06", "testValue06");
// mapSize, clearMap, mapIsEmpty 测试
Assert.assertTrue("putAll operation failed!", cacheStore.putAll(key, expectedMap));
Assert.assertEquals(expectedMap.size(), cacheStore.mapSize(key));
Assert.assertTrue(cacheStore.clearMap(key));
Assert.assertTrue(cacheStore.mapIsEmpty(key));
// removeField 多分支测试
Assert.assertTrue("put operation failed!", cacheStore.put(key, "test00", "testValue00"));
Assert.assertTrue(cacheStore.containsField(key, "test00"));
Assert.assertEquals("testValue00", cacheStore.get(key, "test00"));
Assert.assertTrue("removeField operation failed!", cacheStore.removeField(key, "test00"));
Assert.assertFalse(cacheStore.removeField(key, "test00"));
}
}
@@ -0,0 +1,45 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.local;
import net.lamgc.cgj.bot.cache.CacheKey;
import org.junit.Assert;
import org.junit.Test;
import java.util.HashSet;
import java.util.Set;
import static org.junit.Assert.*;
/**
* @see HashSetCacheStore
*/
public class HashSetCacheStoreTest {
@Test
public void getCacheItemCollection() {
HashSetCacheStore<String> store = new HashSetCacheStore<>();
final CacheKey key = new CacheKey("test");
Assert.assertNull(store.getCacheItemCollection(key, false));
Set<String> collection = store.getCacheItemCollection(key, true);
Assert.assertNotNull(collection);
Assert.assertEquals(collection, store.getCacheItemCollection(key, false));
}
}
@@ -0,0 +1,176 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.local;
import com.google.common.base.Throwables;
import net.lamgc.cgj.bot.cache.CacheKey;
import net.lamgc.cgj.bot.cache.SingleCacheStore;
import org.junit.Assert;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
/**
* @see HashSingleCacheStore
* @see HashCacheStore
*/
public class HashSingleCacheStoreTest {
@Test
public void nullThrowTest() {
final SingleCacheStore<String> cacheStore = new HashSingleCacheStore<>();
final CacheKey key = new CacheKey("testKey");
// HashSingleCacheStore
Assert.assertThrows(NullPointerException.class, () -> cacheStore.set(null, "testValue"));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.set(key, null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.get(null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.setIfNotExist(null, "testValue"));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.setIfNotExist(key, null));
// HashCacheStore
Assert.assertThrows(NullPointerException.class, () -> cacheStore.exists(null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.getTimeToLive(null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.setTimeToLive(null, 0));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.remove(null));
}
@Test
public void setAndGetTest() {
SingleCacheStore<String> cacheStore = new HashSingleCacheStore<>();
final CacheKey key = new CacheKey("testKey");
final String value = "testValue";
Assert.assertTrue("Set operation failed!", cacheStore.set(key, value));
Assert.assertEquals(value, cacheStore.get(key));
Assert.assertTrue("Remove operation failed!", cacheStore.remove(key));
Assert.assertNull("Set operation failed!", cacheStore.get(key));
}
@Test
public void setIfNotExistTest() {
SingleCacheStore<String> cacheStore = new HashSingleCacheStore<>();
final CacheKey key = new CacheKey("testKey");
final String value = "testValue";
final String value2 = "testValue02";
Assert.assertFalse(cacheStore.exists(key));
Assert.assertTrue("The key does not exist but an unexpected result was returned!",
cacheStore.setIfNotExist(key, value));
Assert.assertFalse(cacheStore.setIfNotExist(key, value2));
Assert.assertEquals(value, cacheStore.get(key));
}
@Test
public void expireTest() throws InterruptedException {
final SingleCacheStore<String> cacheStore = new HashSingleCacheStore<>();
final CacheKey key = new CacheKey("testKey");
final String value = "testValue";
// Cache
Assert.assertFalse(cacheStore.setTimeToLive(key, 300));
Assert.assertEquals(-1, cacheStore.getTimeToLive(key));
// TTL 到期被动检查测试: 使用 exists 经 expire 检查失败后返回 false.
Assert.assertTrue("Set operation failed!", cacheStore.set(key, value));
Assert.assertTrue("SetTTL operation failed!", cacheStore.setTimeToLive(key, 200));
Assert.assertNotEquals(-1, cacheStore.getTimeToLive(key));
Thread.sleep(300);
Assert.assertFalse(cacheStore.exists(key));
// 取消 TTL 测试
Assert.assertTrue("Set operation failed!", cacheStore.set(key, value));
Assert.assertTrue("SetTTL operation failed!", cacheStore.setTimeToLive(key, 200));
Assert.assertTrue("SetTTL operation failed!", cacheStore.setTimeToLive(key, -1));
Thread.sleep(300);
Assert.assertTrue(cacheStore.exists(key));
Assert.assertEquals(-1, cacheStore.getTimeToLive(key));
}
@Test
public void removeTest() {
final SingleCacheStore<String> cacheStore = new HashSingleCacheStore<>();
final CacheKey key = new CacheKey("testKey");
final String value = "testValue";
// 删除不存在Cache测试
Assert.assertFalse(cacheStore.remove(key));
// 删除存在的Cache测试
Assert.assertTrue("Set operation failed!", cacheStore.set(key, value));
Assert.assertTrue(cacheStore.remove(key));
}
@Test
public void clearTest() {
final SingleCacheStore<String> cacheStore = new HashSingleCacheStore<>();
final CacheKey key = new CacheKey("testKey");
final String value = "testValue";
Assert.assertTrue("Set operation failed!", cacheStore.set(key, value));
Assert.assertTrue(cacheStore.exists(key));
Assert.assertTrue("Clear operation failed!", cacheStore.clear());
Assert.assertFalse(cacheStore.exists(key));
}
@Test
public void sizeAndKeySetTest() {
Map<String, String> expectedMap = new HashMap<>();
expectedMap.put("test01", "testValue01");
expectedMap.put("test02", "testValue02");
expectedMap.put("test03", "testValue03");
expectedMap.put("test04", "testValue04");
expectedMap.put("test05", "testValue05");
expectedMap.put("test06", "testValue06");
final SingleCacheStore<String> cacheStore = new HashSingleCacheStore<>();
expectedMap.forEach((key, value) -> cacheStore.set(new CacheKey(key), value));
Assert.assertEquals(expectedMap.size(), cacheStore.size());
Assert.assertTrue(expectedMap.keySet().containsAll(cacheStore.keySet()));
}
@Test
public void cleanTest() throws InterruptedException {
SingleCacheStore<String> cacheStore = new HashSingleCacheStore<>();
final CacheKey persistenceKey = new CacheKey("persistenceKey");
final CacheKey expireKey = new CacheKey("expireKey");
final String value = "testValue";
// 过期键与持久键
cacheStore.set(persistenceKey, value);
cacheStore.set(expireKey, value);
cacheStore.setTimeToLive(expireKey, 50);
Thread.sleep(100L);
Cleanable cleanableStore = (Cleanable) cacheStore;
try {
cleanableStore.clean();
} catch (Exception e) {
Assert.fail("Cleaning up expired keys is an exception\n" +
Throwables.getStackTraceAsString(e));
return;
}
Assert.assertTrue(cacheStore.exists(persistenceKey));
Assert.assertFalse(cacheStore.exists(expireKey));
}
}
@@ -0,0 +1,93 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.local;
import net.lamgc.cgj.bot.cache.ListCacheStore;
import net.lamgc.cgj.bot.cache.MapCacheStore;
import net.lamgc.cgj.bot.cache.SetCacheStore;
import net.lamgc.cgj.bot.cache.SingleCacheStore;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import net.lamgc.cgj.bot.cache.convert.StringToStringConverter;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.io.IOException;
/**
* @see LocalCacheStoreFactory
*/
public class LocalCacheStoreFactoryTest {
private final static TemporaryFolder tempFolder = TemporaryFolder.builder()
.build();
private final static String IDENTIFY = "test";
private final static StringConverter<String> CONVERTER = new StringToStringConverter();
private static File storeFolder;
@BeforeClass
public static void beforeProcess() throws IOException {
tempFolder.create();
storeFolder = tempFolder.newFolder("cache", "Local-Memory");
Assert.assertNotNull(createLocalCacheStoreFactory());
}
private static LocalCacheStoreFactory createLocalCacheStoreFactory() {
LocalCacheStoreFactory factory = new LocalCacheStoreFactory();
Assert.assertNotNull(storeFolder);
factory.initial(storeFolder);
return factory;
}
@Test
public void initial() {
Assert.assertNotNull(createLocalCacheStoreFactory());
}
@Test
public void newSingleCacheStore() {
SingleCacheStore<String> cacheStore = createLocalCacheStoreFactory().newSingleCacheStore(IDENTIFY, CONVERTER);
Assert.assertEquals(HashSingleCacheStore.class, cacheStore.getClass());
}
@Test
public void newListCacheStore() {
ListCacheStore<String> cacheStore = createLocalCacheStoreFactory().newListCacheStore(IDENTIFY, CONVERTER);
Assert.assertEquals(CopyOnWriteArrayListCacheStore.class, cacheStore.getClass());
}
@Test
public void newSetCacheStore() {
SetCacheStore<String> cacheStore = createLocalCacheStoreFactory().newSetCacheStore(IDENTIFY, CONVERTER);
Assert.assertEquals(HashSetCacheStore.class, cacheStore.getClass());
}
@Test
public void newMapCacheStore() {
MapCacheStore<String> cacheStore = createLocalCacheStoreFactory().newMapCacheStore(IDENTIFY, CONVERTER);
Assert.assertEquals(HashMapCacheStore.class, cacheStore.getClass());
}
@Test
public void canGetCacheStore() {
Assert.assertTrue(createLocalCacheStoreFactory().canGetCacheStore());
}
}
@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (C) 2021 LamGC
~
~ ContentGrabbingJi is free software: you can redistribute it and/or modify
~ it under the terms of the GNU Affero General Public License as
~ published by the Free Software Foundation, either version 3 of the
~ License.
~
~ ContentGrabbingJi is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU Affero General Public License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ContentGrabbingJi</artifactId>
<groupId>net.lamgc</groupId>
<version>3.0.0-alpha-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ContentGrabbingJi-CacheStore-redis</artifactId>
<build>
<resources>
<resource>
<directory>${project.basedir}/src/main/lua/</directory>
<includes>
<include>*.lua</include>
</includes>
<targetPath>${project.build.outputDirectory}/lua/</targetPath>
</resource>
</resources>
</build>
<dependencies>
<dependency>
<groupId>net.lamgc</groupId>
<artifactId>ContentGrabbingJi-CacheStore-api</artifactId>
<version>3.0.0-alpha-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.9</version>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,50 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.redis;
/**
* @author LamGC
*/
public enum LuaScript {
/**
* [List] 检查元素是否存在.
*/
LIST_CHECK_ELEMENT_CONTAINS("CheckElementContains"),
/**
* [List] 删除指定索引的元素.
*/
LIST_REMOVE_ELEMENT_BY_INDEX("RemoveElementByIndex"),
/**
* [All] 删除所有前缀为指定字符串的键.
*/
STORE_REMOVE_KEYS_BY_PREFIX("RemoveKeysByPrefix")
;
public final static String PACKAGE_PATH = "lua/";
private final String scriptName;
LuaScript(String scriptName) {
this.scriptName = scriptName;
}
public String getScriptName() {
return scriptName;
}
}
@@ -0,0 +1,110 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.redis;
import net.lamgc.cgj.bot.cache.CacheKey;
import net.lamgc.cgj.bot.cache.CacheStore;
import java.util.*;
/**
*
* @author LamGC
*/
public abstract class RedisCacheStore<V> implements CacheStore<V> {
private final RedisConnectionPool connectionPool;
protected RedisCacheStore(RedisConnectionPool connectionPool) {
this.connectionPool = connectionPool;
}
/**
* 获取 Key 前缀.
* <p>key = getKeyPrefix() + key
* @param cacheKey CacheKey 对象.
* @return 返回 Key 前缀.
*/
protected String getKeyString(CacheKey cacheKey) {
return RedisUtils.toRedisCacheKey(getKeyPrefix(), Objects.requireNonNull(cacheKey));
}
/**
* 获取 Key 的完整前缀.
* @return 返回完整前缀.
*/
protected abstract String getKeyPrefix();
@Override
public boolean setTimeToLive(CacheKey key, long ttl) {
String keyString = getKeyString(key);
return connectionPool.executeRedis(jedis -> {
Long result;
if (ttl >= 0) {
result = jedis.pexpire(keyString, ttl);
} else {
result = jedis.persist(keyString);
}
return result.intValue() == RedisUtils.RETURN_CODE_OK;
});
}
@Override
public long getTimeToLive(CacheKey key) {
return connectionPool.executeRedis(jedis -> {
Long ttl = jedis.pttl(getKeyString(key));
return ttl < 0 ? -1 : ttl;
});
}
@Override
public long size() {
return (long) connectionPool.executeRedis(jedis -> jedis.keys(getKeyString(RedisUtils.CACHE_KEY_ALL)).size());
}
@Override
public boolean clear() {
List<String> keys = new ArrayList<>(1);
keys.add(getKeyString(RedisUtils.CACHE_KEY_ALL));
connectionPool.executeScript(LuaScript.STORE_REMOVE_KEYS_BY_PREFIX, keys, null);
return true;
}
@Override
public boolean exists(CacheKey key) {
return connectionPool.executeRedis(jedis -> jedis.exists(getKeyString(key)));
}
@Override
public boolean remove(CacheKey key) {
return connectionPool.executeRedis(jedis -> jedis.del(getKeyString(key)) == RedisUtils.RETURN_CODE_OK);
}
@Override
public Set<String> keySet() {
Set<String> keys = connectionPool.executeRedis(jedis ->
jedis.keys(getKeyString(RedisUtils.CACHE_KEY_ALL)));
final int prefixLength = getKeyPrefix().length();
Set<String> newKeys = new HashSet<>();
for (String key : keys) {
newKeys.add(key.substring(prefixLength + 1));
}
return newKeys;
}
}
@@ -0,0 +1,86 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.redis;
import com.google.gson.Gson;
import net.lamgc.cgj.bot.cache.*;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import net.lamgc.cgj.bot.cache.exception.GetCacheStoreException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.nio.charset.StandardCharsets;
/**
*
* @author LamGC
*/
@Factory(name = "Redis", priority = FactoryPriority.PRIORITY_HIGHER, source = CacheStoreSource.REMOTE)
public class RedisCacheStoreFactory implements CacheStoreFactory {
private final static Logger log = LoggerFactory.getLogger(RedisCacheStoreFactory.class);
private final RedisConnectionPool connectionPool = new RedisConnectionPool();
@Override
public void initial(File dataDirectory) {
final File propertiesFile = new File(dataDirectory, RedisUtils.PROPERTIES_FILE_NAME);
if (!propertiesFile.exists()) {
log.warn("未找到 Redis 配置文件, 使用默认配置.");
return;
} else if (!propertiesFile.isFile()) {
log.warn("Redis 配置文件不是一个文件, 使用默认配置.");
return;
}
try (Reader propertiesReader = new BufferedReader(
new InputStreamReader(new FileInputStream(propertiesFile), StandardCharsets.UTF_8))) {
RedisConnectionProperties properties = new Gson()
.fromJson(propertiesReader, RedisConnectionProperties.class);
connectionPool.setConnectionProperties(properties);
log.debug("Redis 配置文件已成功读取: {}", properties);
} catch (IOException e) {
log.error("读取 Redis 配置文件时发生异常, 将使用默认配置连接 Redis.", e);
}
}
@Override
public <V> SingleCacheStore<V> newSingleCacheStore(String identify, StringConverter<V> converter) {
return new RedisSingleCacheStore<>(connectionPool, identify, converter);
}
@Override
public <E> ListCacheStore<E> newListCacheStore(String identify, StringConverter<E> converter) {
return new RedisListCacheStore<>(connectionPool, identify, converter);
}
@Override
public <E> SetCacheStore<E> newSetCacheStore(String identify, StringConverter<E> converter) {
throw new GetCacheStoreException("No corresponding implementation");
}
@Override
public <V> MapCacheStore<V> newMapCacheStore(String identify, StringConverter<V> converter) {
return new RedisMapCacheStore<>(connectionPool, identify, converter);
}
@Override
public boolean canGetCacheStore() {
return connectionPool.available();
}
}
@@ -0,0 +1,232 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.redis;
import com.google.common.base.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
/**
* 统一的 Redis 连接池.
* @author LamGC
*/
class RedisConnectionPool {
private final static Logger log = LoggerFactory.getLogger(RedisConnectionPool.class);
private final AtomicReference<JedisPool> POOL = new AtomicReference<>();
private final AtomicReference<RedisConnectionProperties> connectionProperties = new AtomicReference<>();
private final Map<LuaScript, String> scriptMap = new HashMap<>();
public RedisConnectionPool() {
Runtime.getRuntime().addShutdownHook(new Thread(this::close,
"Shutdown-RedisConnectionPool@" + Integer.toHexString(this.hashCode())));
}
public synchronized void setConnectionProperties(RedisConnectionProperties properties) {
if(connectionProperties.get() != null) {
connectionProperties.set(properties);
}
}
public synchronized void reconnectRedis() {
JedisPool jedisPool = POOL.get();
if (jedisPool != null && !jedisPool.isClosed()) {
return;
}
JedisPoolConfig config = new JedisPoolConfig();
config.setTestOnBorrow(true);
config.setTestOnReturn(true);
RedisConnectionProperties properties = connectionProperties.get();
if (properties == null) {
jedisPool = new JedisPool(config);
} else {
String userName = properties.getUserName();
if (Strings.isNullOrEmpty(userName)) {
jedisPool = new JedisPool(config,
properties.getHost(),
properties.getPort(),
properties.getConnectionTimeout(),
properties.getSocketTimeout(),
properties.getPassword(),
properties.getDatabaseId(),
properties.getClientName(),
properties.enableSsl()
);
} else {
jedisPool = new JedisPool(config,
properties.getHost(),
properties.getPort(),
properties.getConnectionTimeout(),
properties.getSocketTimeout(),
userName,
properties.getPassword(),
properties.getDatabaseId(),
properties.getClientName(),
properties.enableSsl()
);
}
}
POOL.set(jedisPool);
loadScript();
}
/**
* 获取一个 Jedis 对象.
* <p>注意, 需回收 Jedis 对象, 否则可能会耗尽连接池导致后续操作受到影响.
* @return 返回可用的 Jedis 连接.
*/
public Jedis getConnection() {
JedisPool pool = POOL.get();
if (pool == null || pool.isClosed()) {
reconnectRedis();
pool = POOL.get();
if (pool == null) {
throw new IllegalStateException("Redis connection lost");
}
}
return pool.getResource();
}
/**
* 执行 Redis 操作并返回相关值.
* <p>本方法会自动回收 Jedis.
* @param function 待运行的操作.
* @param <R> 返回值类型.
* @return 返回 function 返回的内容.
*/
public <R> R executeRedis(Function<Jedis, R> function) {
try (Jedis jedis = getConnection()) {
return function.apply(jedis);
}
}
/**
* 检查 Redis 连接池是否有可用的资源.
* @return 如果连接池依然活跃, 返回 true.
*/
public boolean available() {
JedisPool jedisPool = POOL.get();
if (jedisPool == null || jedisPool.isClosed()) {
reconnectRedis();
jedisPool = POOL.get();
if (jedisPool == null || jedisPool.isClosed()) {
return false;
}
}
if (jedisPool.getNumIdle() == 0) {
try (Jedis jedis = jedisPool.getResource()) {
return "pong".equalsIgnoreCase(jedis.ping());
} catch (Exception e) {
log.error("Redis 连接测试时发生异常", e);
return false;
}
}
return true;
}
/**
* 获取指定脚本的 Sha.
* @param script 脚本.
* @return 如果存在, 返回 Sha, 否则返回 null.
*/
public String getScriptSha(LuaScript script) {
return scriptMap.get(script);
}
/**
* 加载脚本.
*/
private void loadScript() {
for (LuaScript script : LuaScript.values()) {
InputStream scriptStream = this.getClass().
getResourceAsStream("/" + LuaScript.PACKAGE_PATH + script.getScriptName() + ".lua");
if (scriptStream == null) {
log.warn("脚本 {} 获取失败, 相关操作将无法使用, 请检查缓存组件是否损坏.", script.getScriptName());
continue;
}
String scriptContent;
try (BufferedReader reader = new BufferedReader(new InputStreamReader(scriptStream, StandardCharsets.UTF_8))) {
String line;
StringBuilder builder = new StringBuilder();
while((line = reader.readLine()) != null) {
builder.append(line).append('\n');
}
scriptContent = builder.toString();
} catch (IOException e) {
log.error("读取脚本文件时发生异常.(Script: " + script.getScriptName() + ")", e);
continue;
}
try {
String scriptSha = executeRedis(jedis -> jedis.scriptLoad(scriptContent));
if (scriptSha != null) {
scriptMap.put(script, scriptSha);
log.debug("脚本 {} 已成功加载.(Sha: {})", script, scriptSha);
}
} catch (Exception e) {
log.error("加载脚本时发生异常.(Script: " + script.getScriptName() + ")", e);
}
}
}
/**
* 执行脚本.
* @param script Lua 脚本.
* @param keys 待传入脚本的键列表.
* @param args 待传入脚本的参数列表.
* @return 如果成功, 返回脚本所返回的数据, 需根据脚本实际返回转换对象.
* @throws NullPointerException 当 script 为 {@code null} 时抛出.
*/
public Object executeScript(final LuaScript script, final List<String> keys, final List<String> args) {
String scriptSha = this.getScriptSha(Objects.requireNonNull(script));
if (scriptSha == null) {
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
log.warn("脚本未加载, 方法 {}() 无法执行(方法存在于Class {}:{}). (所需脚本: {})",
stackTraceElements[2].getMethodName(),
stackTraceElements[2].getClassName(),
stackTraceElements[2].getLineNumber(),
script.getScriptName());
return false;
}
return executeRedis(jedis -> {
List<String> keysList = (keys == null) ? Collections.emptyList() : keys;
List<String> argsList = (args == null) ? Collections.emptyList() : args;
return jedis.evalsha(scriptSha, keysList, argsList);
});
}
public void close() {
JedisPool jedisPool = POOL.get();
if (jedisPool != null && !jedisPool.isClosed()) {
jedisPool.close();
}
}
}
@@ -0,0 +1,128 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.redis;
import com.google.common.base.Strings;
import redis.clients.jedis.Protocol;
/**
* Redis 连接配置对象.
* @author LamGC
*/
@SuppressWarnings({"FieldCanBeLocal", "unused"})
public class RedisConnectionProperties {
private int connectionTimeout = Protocol.DEFAULT_TIMEOUT;
private int socketTimeout = Protocol.DEFAULT_TIMEOUT;
private String host = Protocol.DEFAULT_HOST;
private int port = Protocol.DEFAULT_PORT;
private boolean ssl = false;
private String userName = null;
private String password = null;
private int databaseId = Protocol.DEFAULT_DATABASE;
private String clientName = null;
@Override
public String toString() {
int showPasswordLength = password.length() / 4;
return "RedisConnectionProperties{" +
"connectionTimeout=" + connectionTimeout +
", socketTimeout=" + socketTimeout +
", host='" + host + '\'' +
", port=" + port +
", ssl=" + ssl +
", userName='" + userName + '\'' +
", password='" + password.substring(0, showPasswordLength) +
Strings.repeat("*", password.length() - showPasswordLength) + '\'' +
", databaseId=" + databaseId +
", clientName='" + clientName + '\'' +
'}';
}
public int getConnectionTimeout() {
return connectionTimeout;
}
public int getSocketTimeout() {
return socketTimeout;
}
public String getHost() {
return host;
}
public int getPort() {
return port;
}
public boolean enableSsl() {
return ssl;
}
public String getUserName() {
return userName;
}
public String getPassword() {
return password;
}
public int getDatabaseId() {
return databaseId;
}
public String getClientName() {
return clientName;
}
public void setClientName(String clientName) {
this.clientName = clientName;
}
public void setConnectionTimeout(int connectionTimeout) {
this.connectionTimeout = connectionTimeout;
}
public void setDatabaseId(int databaseId) {
this.databaseId = databaseId;
}
public void setHost(String host) {
this.host = host;
}
public void setPassword(String password) {
this.password = password;
}
public void setPort(int port) {
this.port = port;
}
public void setSocketTimeout(int socketTimeout) {
this.socketTimeout = socketTimeout;
}
public void setEnableSsl(boolean ssl) {
this.ssl = ssl;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
@@ -0,0 +1,158 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.redis;
import net.lamgc.cgj.bot.cache.CacheKey;
import net.lamgc.cgj.bot.cache.ListCacheStore;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
/**
* Redis 列表缓存存储容器.
* @param <E>
* @author LamGC
*/
public class RedisListCacheStore<E> extends RedisCacheStore<List<E>> implements ListCacheStore<E> {
private final String keyPrefix;
private final StringConverter<E> converter;
private final RedisConnectionPool connectionPool;
public RedisListCacheStore(RedisConnectionPool connectionPool, String keyPrefix, StringConverter<E> converter) {
super(connectionPool);
this.connectionPool = connectionPool;
keyPrefix = Objects.requireNonNull(keyPrefix).trim();
if (keyPrefix.isEmpty()) {
throw new IllegalArgumentException("Key prefix cannot be empty.");
}
if (keyPrefix.endsWith(RedisUtils.KEY_SEPARATOR)) {
this.keyPrefix = keyPrefix;
} else {
this.keyPrefix = keyPrefix + RedisUtils.KEY_SEPARATOR;
}
this.converter = Objects.requireNonNull(converter);
}
@Override
public E getElement(CacheKey key, int index) {
return connectionPool.executeRedis(jedis -> converter.from(jedis.lindex(getKeyString(key), index)));
}
@Override
public List<E> getElementsByRange(CacheKey key, int index, int length) {
List<String> strings = connectionPool.executeRedis(jedis ->
// stop = start + length - 1
jedis.lrange(getKeyString(key), index, index + length - 1));
List<E> result = new ArrayList<>(strings.size());
strings.forEach(element -> result.add(converter.from(element)));
return result;
}
@Override
public boolean removeElement(CacheKey key, int index) {
List<String> keys = new ArrayList<>(1);
List<String> args = new ArrayList<>(1);
keys.add(getKeyString(key));
args.add(String.valueOf(index));
Number result = (Number) connectionPool.executeScript(LuaScript.LIST_REMOVE_ELEMENT_BY_INDEX, keys, args);
return result.intValue() == 1;
}
@Override
public boolean removeElement(CacheKey key, E element) {
return connectionPool.executeRedis(jedis ->
jedis.lrem(getKeyString(key), 1, converter.to(element)) != RedisUtils.RETURN_CODE_FAILED);
}
@Override
public boolean addElement(CacheKey key, E element) {
Objects.requireNonNull(element);
connectionPool.executeRedis(jedis ->
jedis.lpush(getKeyString(key), converter.to(element)) != RedisUtils.RETURN_CODE_FAILED);
return true;
}
@Override
public boolean addElements(CacheKey key, Collection<E> elements) {
Objects.requireNonNull(elements);
if (elements.size() == 0) {
return exists(key);
}
List<E> values = new ArrayList<>(elements);
String[] valueStrings = new String[values.size()];
for (int i = 0; i < valueStrings.length; i++) {
valueStrings[i] = converter.to(values.get(i));
}
connectionPool.executeRedis(jedis ->
jedis.lpush(getKeyString(key), valueStrings) != RedisUtils.RETURN_CODE_FAILED);
return true;
}
/**
* {@inheritDoc}
*
* <p> 注意: 在 Redis 实现中, 该功能通过一段 Lua 脚本实现,
* 由于 Redis 并没有原生支持该功能, 所以只能用脚本遍历查找.
* 如果 List 元素过多, 可能会导致执行缓慢且影响后续操作, 谨慎使用.
* @param key 待检查的缓存项键名.
* @param element 待查找的缓存值.
* @return 如果存在, 返回 true, 如果元素不存在, 或缓存项不存在, 返回 false.
* @throws NullPointerException 当 key 或 element 为 null 时抛出; 本方法不允许存储 null 值, 因为 null 代表"没有/不存在".
*/
@Override
public boolean containsElement(CacheKey key, E element) {
List<String> keys = new ArrayList<>(1);
List<String> args = new ArrayList<>(1);
keys.add(getKeyString(key));
args.add(converter.to(element));
Number result = (Number) connectionPool.executeScript(LuaScript.LIST_CHECK_ELEMENT_CONTAINS, keys, args);
return result.intValue() != -1;
}
@Override
public boolean isEmpty(CacheKey key) {
return elementsLength(key) == -1;
}
@Override
public int elementsLength(CacheKey key) {
long result = connectionPool.executeRedis(jedis -> jedis.llen(getKeyString(key)));
if (result == 0) {
return -1;
} else {
return (int) result;
}
}
@Override
public boolean clearCollection(CacheKey key) {
return remove(key);
}
@Override
protected String getKeyPrefix() {
return this.keyPrefix;
}
}
@@ -0,0 +1,171 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.redis;
import net.lamgc.cgj.bot.cache.CacheKey;
import net.lamgc.cgj.bot.cache.MapCacheStore;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import java.util.*;
/**
* Redis Map缓存存储容器.
* @param <V> 值类型.
* @author LamGC
*/
public class RedisMapCacheStore<V> extends RedisCacheStore<Map<String, V>> implements MapCacheStore<V> {
private final String keyPrefix;
private final StringConverter<V> converter;
private final RedisConnectionPool connectionPool;
public RedisMapCacheStore(RedisConnectionPool connectionPool, String keyPrefix, StringConverter<V> converter) {
super(connectionPool);
this.connectionPool = connectionPool;
keyPrefix = Objects.requireNonNull(keyPrefix).trim();
if (keyPrefix.isEmpty()) {
throw new IllegalArgumentException("Key prefix cannot be empty.");
}
if (keyPrefix.endsWith(RedisUtils.KEY_SEPARATOR)) {
this.keyPrefix = keyPrefix;
} else {
this.keyPrefix = keyPrefix + RedisUtils.KEY_SEPARATOR;
}
this.converter = Objects.requireNonNull(converter);
}
@Override
protected String getKeyPrefix() {
return this.keyPrefix;
}
@Override
public int mapSize(CacheKey key) {
return connectionPool.executeRedis(jedis -> {
String keyString = getKeyString(key);
if (jedis.exists(keyString)) {
return jedis.hlen(keyString).intValue();
}
return 0;
});
}
@Override
public Set<String> mapFieldSet(CacheKey key) {
return connectionPool.executeRedis(jedis -> {
String keyString = getKeyString(key);
if (jedis.exists(keyString)) {
return jedis.hkeys(keyString);
}
return null;
});
}
@Override
public Set<V> mapValueSet(CacheKey key) {
List<String> rawValueSet = connectionPool.executeRedis(jedis -> {
String keyString = getKeyString(key);
if (jedis.exists(keyString)) {
return jedis.hvals(keyString);
}
return null;
});
if (rawValueSet == null) {
return null;
}
Set<V> result = new HashSet<>();
for (String rawValue : rawValueSet) {
result.add(converter.from(rawValue));
}
return result;
}
@Override
public boolean put(CacheKey key, String field, V value) {
Objects.requireNonNull(field);
Objects.requireNonNull(value);
connectionPool.executeRedis(jedis -> {
String keyString = getKeyString(key);
return jedis.hset(keyString, field, converter.to(value));
});
return true;
}
@Override
public boolean putAll(CacheKey key, Map<? extends String, ? extends V> map) {
Objects.requireNonNull(key);
Objects.requireNonNull(map);
if (map.size() == 0) {
return exists(key);
}
final Map<String, String> targetMap = new HashMap<>(map.size());
map.forEach((k, v) -> targetMap.put(k, converter.to(v)));
return connectionPool.executeRedis(jedis -> {
String keyString = getKeyString(key);
return RedisUtils.isOk(jedis.hmset(keyString, targetMap));
});
}
@Override
public boolean putIfNotExist(CacheKey key, String field, V value) {
Objects.requireNonNull(field);
Objects.requireNonNull(value);
return connectionPool.executeRedis(jedis -> {
String keyString = getKeyString(key);
return jedis.hsetnx(keyString, field, converter.to(value)) == RedisUtils.RETURN_CODE_OK;
});
}
@Override
public V get(CacheKey key, String field) {
Objects.requireNonNull(field);
String value = connectionPool.executeRedis(jedis -> jedis.hget(getKeyString(key), field));
if (value == null) {
return null;
}
return converter.from(value);
}
@Override
public boolean removeField(CacheKey key, String field) {
Objects.requireNonNull(field);
return connectionPool.executeRedis(jedis ->
jedis.hdel(getKeyString(key), field) == RedisUtils.RETURN_CODE_OK);
}
@Override
public boolean containsField(CacheKey key, String field) {
Objects.requireNonNull(field);
return connectionPool.executeRedis(jedis -> jedis.hexists(getKeyString(key), field));
}
@Override
public boolean mapIsEmpty(CacheKey key) {
return mapSize(key) == 0;
}
@Override
public boolean clearMap(CacheKey key) {
return remove(key);
}
}
@@ -0,0 +1,82 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.redis;
import net.lamgc.cgj.bot.cache.CacheKey;
import net.lamgc.cgj.bot.cache.SingleCacheStore;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import java.util.Objects;
/**
* Redis 单项缓存存储容器.
* @param <V> 值类型.
* @see net.lamgc.cgj.bot.cache.CacheStore
* @see net.lamgc.cgj.bot.cache.SingleCacheStore
* @see net.lamgc.cgj.bot.cache.redis.RedisCacheStore
* @author LamGC
*/
public class RedisSingleCacheStore<V> extends RedisCacheStore<V> implements SingleCacheStore<V> {
private final String keyPrefix;
private final StringConverter<V> converter;
private final RedisConnectionPool connectionPool;
public RedisSingleCacheStore(RedisConnectionPool connectionPool, String keyPrefix, StringConverter<V> converter) {
super(connectionPool);
this.connectionPool = connectionPool;
keyPrefix = Objects.requireNonNull(keyPrefix).trim();
if (keyPrefix.isEmpty()) {
throw new IllegalArgumentException("Key prefix cannot be empty.");
}
if (keyPrefix.endsWith(RedisUtils.KEY_SEPARATOR)) {
this.keyPrefix = keyPrefix;
} else {
this.keyPrefix = keyPrefix + RedisUtils.KEY_SEPARATOR;
}
this.converter = Objects.requireNonNull(converter);
}
@Override
public boolean set(CacheKey key, V value) {
return connectionPool.executeRedis(jedis ->
RedisUtils.isOk(jedis.set(getKeyString(key), converter.to(Objects.requireNonNull(value)))));
}
@Override
public boolean setIfNotExist(CacheKey key, V value) {
return connectionPool.executeRedis(jedis ->
jedis.setnx(getKeyString(key), converter.to(Objects.requireNonNull(value)))
== RedisUtils.RETURN_CODE_OK);
}
@Override
public V get(CacheKey key) {
String value = connectionPool.executeRedis(jedis -> jedis.get(getKeyString(key)));
if (value == null) {
return null;
}
return converter.from(value);
}
@Override
protected String getKeyPrefix() {
return this.keyPrefix;
}
}
@@ -0,0 +1,77 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.redis;
import net.lamgc.cgj.bot.cache.CacheKey;
/**
* @author LamGC
*/
public class RedisUtils {
/**
* 返回码 - 成功
*/
public final static int RETURN_CODE_OK = 1;
/**
* 返回码 - 失败
*/
public final static int RETURN_CODE_FAILED = 0;
/**
* Key 匹配规则 - 所有 Key
*/
public final static String KEY_PATTERN_ALL = "*";
/**
* 特殊缓存键 - 所有 Key.
*/
public final static CacheKey CACHE_KEY_ALL = new CacheKey(KEY_PATTERN_ALL);
/**
* Key 分隔符
*/
public final static String KEY_SEPARATOR = ":";
/**
* Redis 组件配置文件名.
*/
public final static String PROPERTIES_FILE_NAME = "redis.properties.json";
/**
* 检查字符串返回结果是否为操作成功.
* @param result 字符串返回结果.
* @return 如果为操作成功, 返回 true.
*/
public static boolean isOk(String result) {
return "OK".equalsIgnoreCase(result);
}
/**
* 将 {@link CacheKey} 转换为 Redis 的标准 key 格式.
* @param keyPrefix Key 前缀.
* @param cacheKey 缓存键对象.
* @return 返回格式化后的 Key.
*/
public static String toRedisCacheKey(String keyPrefix, CacheKey cacheKey) {
return (keyPrefix.endsWith(KEY_SEPARATOR) ? keyPrefix : (keyPrefix + KEY_SEPARATOR)) +
cacheKey.join(RedisUtils.KEY_SEPARATOR);
}
}
@@ -0,0 +1,25 @@
local key = tostring(KEYS[1])
local value = ARGV[1]
local step = 175
if (key == nil) or (value == nil) then
return -1
end
if not (ARGV[2] == nil) then
step = tonumber(ARGV[2])
end
-- 步进循环搜索, 找到了立刻返回
local listLength = redis.call('llen', key);
for i = 0, listLength, step do
local storeValue = redis.call("lrange", key, i, i + step)
for listIndex, v in ipairs(storeValue) do
if value == v then
return i + listIndex - 1;
end
end
end
-- 如果没找到索引, 返回 0
return -1
@@ -0,0 +1,31 @@
local function getRandom(n)
local t = {
"0","1","2","3","4","5","6","7","8","9",
"a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z",
"A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z",
}
local s = ""
for _ = 1, n do
s = s .. t[math.random(#t)]
end;
return s
end
local key = tostring(KEYS[1])
local index = tonumber(ARGV[1])
if (key == nil) or (index == nil) or (redis.call("exists", key) == false) then
return 0
end
local listLength = redis.call("llen", key)
if listLength == 0 then
return 0
end
if (index < 0) or (index >= listLength) or redis.call("lIndex", key, index) == nil then
return 0
else
local flag = getRandom(24)
redis.call("lSet", key, index, flag);
return redis.call("lRem", key, 0, flag);
end
@@ -0,0 +1,18 @@
local keyPrefix = tostring(KEYS[1])
if (keyPrefix == nil) then
return false
end
if (string.sub(keyPrefix, string.len(keyPrefix)) ~= "*") then
keyPrefix = keyPrefix .. "*"
end
local keys = redis.call("KEYS", keyPrefix)
local count = 0;
for _, v in ipairs(keys) do
count = count + redis.call("DEL", v)
end
return count
@@ -0,0 +1,35 @@
#
# Copyright (C) 2021 LamGC
#
# ContentGrabbingJi is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License.
#
# ContentGrabbingJi is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
#
# Copyright (C) 2020 LamGC
#
# ContentGrabbingJi is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# ContentGrabbingJi is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
net.lamgc.cgj.bot.cache.redis.RedisCacheStoreFactory
@@ -0,0 +1,211 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.redis;
import net.lamgc.cgj.bot.cache.CacheKey;
import org.junit.*;
import redis.clients.jedis.Jedis;
import java.util.HashSet;
import java.util.Set;
import static net.lamgc.cgj.bot.cache.redis.util.RedisTestUtils.assertDeleteIfExist;
import static net.lamgc.cgj.bot.cache.redis.util.RedisTestUtils.randomString;
/**
* @see RedisCacheStore
*/
public class RedisCacheStoreTest {
private final static String KEY_PREFIX = "test:store";
private static RedisConnectionPool connectionPool;
private static RedisCacheStore<String> cacheStore;
private static Jedis jedis;
@BeforeClass
public static void beforeAllTest() {
jedis = new Jedis();
connectionPool = new RedisConnectionPool();
cacheStore = new SimpleRedisCacheStore(connectionPool, KEY_PREFIX);
}
@AfterClass
public static void afterAllTest() {
if (jedis != null && jedis.isConnected()) {
jedis.close();
}
connectionPool.close();
}
@Before
public void beforeTest() {
clearAllKey();
}
@After
public void afterTest() {
clearAllKey();
}
private void clearAllKey() {
Set<String> keys = jedis.keys(RedisUtils.toRedisCacheKey(KEY_PREFIX, RedisUtils.CACHE_KEY_ALL));
if (keys.isEmpty()) {
return;
}
String[] keyArray = new String[keys.size()];
keys.toArray(keyArray);
Assert.assertEquals("Failed to delete test key.",
keyArray.length, jedis.del(keyArray).intValue());
}
@Test
public void setTimeToLiveTest() {
final CacheKey key = new CacheKey("test_set_time_to_live");
final String keyString = RedisUtils.toRedisCacheKey(KEY_PREFIX, key);
final long timeToLive = 500;
assertDeleteIfExist(jedis, keyString);
Assert.assertFalse("TTL set successfully for nonexistent key.", cacheStore.setTimeToLive(key, timeToLive));
Assert.assertTrue("Failed to set key value for test.", RedisUtils.isOk(jedis.set(keyString, "value")));
Assert.assertTrue("Failed to set TTL on key value.", cacheStore.setTimeToLive(key, timeToLive));
long currentTTL = jedis.ttl(keyString);
Assert.assertTrue("TTL was not set successfully.", currentTTL >= 0);
Assert.assertTrue("The actual set TTL is greater than expected.",
currentTTL < timeToLive);
Assert.assertTrue("Failed to clear TTL on key value.", cacheStore.setTimeToLive(key, -1));
currentTTL = jedis.ttl(keyString);
Assert.assertTrue("TTL was not clear successfully.", currentTTL < 0);
}
@Test
public void getTimeToLiveTest() {
final CacheKey key = new CacheKey("test_get_time_to_live");
final String keyString = RedisUtils.toRedisCacheKey(KEY_PREFIX, key);
final long timeToLive = 500;
assertDeleteIfExist(jedis, keyString);
Assert.assertEquals("The getTimeToLive() method returned a positive integer for a nonexistent key.",
-1, cacheStore.getTimeToLive(key));
Assert.assertTrue("Failed to set key value for test.", RedisUtils.isOk(jedis.set(keyString, "value")));
Assert.assertEquals("Failed to set TTL on key value.", 1, jedis.pexpire(keyString, timeToLive).intValue());
long currentTTL = cacheStore.getTimeToLive(key);
Assert.assertTrue("TTL failed to get.", currentTTL > 0);
Assert.assertTrue("The actual set TTL is greater than expected.",
currentTTL <= timeToLive);
Assert.assertTrue("The actual set TTL is greater than expected.",
currentTTL >= (timeToLive - 5));
Assert.assertEquals("Failed to clear TTL on key value.",
1, jedis.persist(keyString).intValue());
currentTTL = cacheStore.getTimeToLive(key);
Assert.assertTrue("TTL was not clear successfully.", currentTTL < 0);
}
@Test
public void sizeTest() {
final String keyPrefix =
RedisUtils.toRedisCacheKey(KEY_PREFIX, new CacheKey("test_size")) + ':';
final String allKey = RedisUtils.toRedisCacheKey(keyPrefix, RedisUtils.CACHE_KEY_ALL);
for (int i = 0; i < 50; i++) {
Assert.assertTrue("Failed to add key.", RedisUtils.isOk(
jedis.set(
RedisUtils.toRedisCacheKey(keyPrefix, new CacheKey(randomString(8))),
randomString(10)
)));
Assert.assertEquals("Inconsistent number of keys.", jedis.keys(allKey).size(), cacheStore.size());
}
}
@Test
public void existsTest() {
final CacheKey key = new CacheKey("test_exists");
final String keyString = RedisUtils.toRedisCacheKey(KEY_PREFIX, key);
assertDeleteIfExist(jedis, keyString);
Assert.assertFalse("The exists() method returned 'true' for a nonexistent key.", cacheStore.exists(key));
Assert.assertTrue("Failed to set key value for test.", RedisUtils.isOk(jedis.set(keyString, "value")));
Assert.assertTrue("The exists() method returned 'false' for the existing key.", cacheStore.exists(key));
}
@Test
public void removeTest() {
final CacheKey key = new CacheKey("test_remove");
final String keyString = RedisUtils.toRedisCacheKey(KEY_PREFIX, key);
assertDeleteIfExist(jedis, keyString);
Assert.assertFalse("Attempt to delete nonexistent key succeeded.", cacheStore.remove(key));
Assert.assertTrue("Failed to set key value for test.", RedisUtils.isOk(jedis.set(keyString, "value")));
Assert.assertTrue("Key deletion failed.", cacheStore.remove(key));
}
@Test
public void clearTest() {
final CacheKey key = new CacheKey("test_clear");
final String keyString = RedisUtils.toRedisCacheKey(KEY_PREFIX, key);
final String externalKey = "test:external";
Assert.assertTrue("Failed to set external key.", RedisUtils.isOk(jedis.set(externalKey, "externalValue")));
Assert.assertTrue("Failed to set test key.", RedisUtils.isOk(jedis.set(keyString, "testValue")));
Assert.assertNotEquals("The cache store is empty.",
0, jedis.keys(RedisUtils.toRedisCacheKey(KEY_PREFIX, RedisUtils.CACHE_KEY_ALL)).size());
Assert.assertTrue(cacheStore.clear());
Assert.assertEquals("The cache store is not empty.",
0, jedis.keys(RedisUtils.toRedisCacheKey(KEY_PREFIX, RedisUtils.CACHE_KEY_ALL)).size());
Assert.assertTrue("External key removed.", jedis.exists(externalKey));
}
@Test
public void keySetTest() {
clearAllKey();
Set<String> actualKeys = cacheStore.keySet();
Assert.assertNotNull("keySet() returned 'null'.", actualKeys);
Assert.assertEquals("CacheStore is empty, but keySet() is not empty.", 0, actualKeys.size());
Set<String> expectKeys = new HashSet<>();
for (int i = 0; i < 50; i++) {
String nextKey = randomString(10);
expectKeys.add(nextKey);
Assert.assertTrue("Failed to set key value for test.",
RedisUtils.isOk(jedis.set(KEY_PREFIX + RedisUtils.KEY_SEPARATOR + nextKey, nextKey)));
}
actualKeys = cacheStore.keySet();
Assert.assertNotNull("keySet() returned 'null'.", actualKeys);
Assert.assertEquals("Number of keys not as expected.", expectKeys.size(), actualKeys.size());
for (String actualKey : actualKeys) {
Assert.assertTrue(String.format("Key '%s' does not belong to the expected key.", actualKey),
expectKeys.contains(actualKey));
}
}
}
@@ -0,0 +1,379 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.redis;
import net.lamgc.cgj.bot.cache.CacheKey;
import net.lamgc.cgj.bot.cache.ListCacheStore;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import net.lamgc.cgj.bot.cache.convert.StringToStringConverter;
import org.junit.*;
import redis.clients.jedis.Jedis;
import java.lang.reflect.Field;
import java.util.*;
/**
* @see RedisListCacheStore
*/
public class RedisListCacheStoreTest {
private static Jedis jedis;
private final static StringConverter<String> CONVERTER = new StringToStringConverter();
private final static String IDENTIFY = "test:list";
private static RedisConnectionPool connectionPool;
@BeforeClass
public static void beforeAllTest() {
jedis = new Jedis();
connectionPool = new RedisConnectionPool();
connectionPool.reconnectRedis();
Assert.assertTrue("Redis is not connected.", connectionPool.available());
}
@AfterClass
public static void afterAllTest() {
if (jedis != null) {
jedis.close();
}
}
private ListCacheStore<String> newListCacheStore() {
return new RedisListCacheStore<>(connectionPool, IDENTIFY, CONVERTER);
}
private Set<String> getListElements(String key) {
Set<String> actualElements = new HashSet<>();
for (long i = 0; i < jedis.llen(key); i++) {
actualElements.add(jedis.lindex(key, i));
}
return actualElements;
}
@Before
public void beforeTest() {
Set<String> keys = jedis.keys(RedisUtils.toRedisCacheKey(IDENTIFY, RedisUtils.CACHE_KEY_ALL));
for (String key : keys) {
jedis.del(key);
}
}
@Test
public void prefixCheck() throws NoSuchFieldException, IllegalAccessException {
final Field prefixField = RedisListCacheStore.class.getDeclaredField("keyPrefix");
prefixField.setAccessible(true);
String prefix = (String) prefixField.get(new RedisListCacheStore<>(connectionPool, IDENTIFY, CONVERTER));
Assert.assertTrue("The prefix does not contain a separator at the end.",
prefix.endsWith(RedisUtils.KEY_SEPARATOR));
prefix = (String) prefixField.get(new RedisListCacheStore<>(connectionPool,
IDENTIFY + RedisUtils.KEY_SEPARATOR, CONVERTER));
Assert.assertTrue("The separator at the end of the prefix is missing.",
prefix.endsWith(RedisUtils.KEY_SEPARATOR));
prefixField.setAccessible(false);
}
@Test(expected = IllegalArgumentException.class)
public void emptyPrefixTest() {
new RedisListCacheStore<>(connectionPool, "", CONVERTER);
}
@Test(expected = NullPointerException.class)
public void nullPrefixTest() {
new RedisListCacheStore<>(connectionPool, null, CONVERTER);
}
@Test
public void addElementTest() {
ListCacheStore<String> listCacheStore = newListCacheStore();
final CacheKey listKey = new CacheKey("list_add_element");
final String element = "test";
final String listKeyStr = RedisUtils.toRedisCacheKey(IDENTIFY, listKey);
final int lastCount = jedis.llen(listKeyStr).intValue();
Assert.assertTrue("The operation to be tested failed.",
listCacheStore.addElement(listKey, element));
Assert.assertEquals("The key value has no change to the quantity.",
lastCount + 1, jedis.llen(listKeyStr).intValue());
Assert.assertEquals("The value of pop is different from that of push.",
element, jedis.lpop(listKeyStr));
}
@Test
public void addElementsTest() {
ListCacheStore<String> listCacheStore = newListCacheStore();
final CacheKey listKey = new CacheKey("list_add_elements");
final String listKeyStr = RedisUtils.toRedisCacheKey(IDENTIFY, listKey);
final Set<String> expectedElements = new HashSet<>();
expectedElements.add("a");
expectedElements.add("b");
expectedElements.add("c");
expectedElements.add("d");
expectedElements.add("e");
Assert.assertEquals("The key does not exist, but the empty collection was added successfully.",
jedis.exists(listKeyStr),
listCacheStore.addElements(listKey, Collections.emptyList()));
Assert.assertTrue("The operation to be tested failed.",
listCacheStore.addElements(listKey, expectedElements));
Assert.assertEquals("The quantity is not as expected.",
expectedElements.size(), jedis.llen(listKeyStr).intValue());
Set<String> actualElements = getListElements(listKeyStr);
Assert.assertTrue("The added value is not as expected.",
actualElements.containsAll(expectedElements));
Assert.assertEquals("Key does not exist, but adding empty collection failed.",
jedis.exists(listKeyStr),
listCacheStore.addElements(listKey, Collections.emptySet()));
}
@Test
public void removeElementByElementTest() {
ListCacheStore<String> listCacheStore = newListCacheStore();
final CacheKey listKey = new CacheKey("list_remove_element_by_element");
final String listKeyStr = RedisUtils.toRedisCacheKey(IDENTIFY, listKey);
final List<String> expectedElements = new ArrayList<>();
expectedElements.add("a");
expectedElements.add("b");
expectedElements.add("c");
expectedElements.add("d");
expectedElements.add("e");
final String[] expectedElementsArr = new String[expectedElements.size()];
expectedElements.toArray(expectedElementsArr);
jedis.del(listKeyStr);
Assert.assertFalse("The operation to be tested failed.",
listCacheStore.removeElement(listKey, "NoExistElement"));
Assert.assertNotEquals("The expected create operation failed.",
RedisUtils.RETURN_CODE_FAILED, jedis.lpush(listKeyStr, expectedElementsArr).intValue());
Random random = new Random();
final int deletedIndex = random.nextInt(expectedElements.size());
Assert.assertTrue("The operation to be tested failed.",
listCacheStore.removeElement(listKey, expectedElements.get(deletedIndex)));
expectedElements.remove(deletedIndex);
Assert.assertEquals("The quantity is not as expected.",
expectedElements.size(), jedis.llen(listKeyStr).intValue());
Set<String> actualElements = getListElements(listKeyStr);
Assert.assertTrue("The added value is not as expected.",
actualElements.containsAll(expectedElements));
}
@Test
public void removeElementByIndexTest() {
ListCacheStore<String> listCacheStore = newListCacheStore();
final CacheKey listKey = new CacheKey("list_remove_element_by_index");
final String listKeyStr = RedisUtils.toRedisCacheKey(IDENTIFY, listKey);
final Set<String> expectedElements = new HashSet<>();
expectedElements.add("a");
expectedElements.add("b");
expectedElements.add("c");
expectedElements.add("d");
expectedElements.add("e");
final String[] expectedElementsArr = new String[expectedElements.size()];
expectedElements.toArray(expectedElementsArr);
jedis.del(listKeyStr);
// 尝试删除不存在的 Key
Assert.assertFalse("The attempt to delete a value that does not exist succeeded.",
listCacheStore.removeElement(listKey, 0));
Assert.assertNotEquals("The expected create operation failed.",
RedisUtils.RETURN_CODE_FAILED, jedis.lpush(listKeyStr, expectedElementsArr).intValue());
Assert.assertFalse("The attempt to delete the value of the illegal index succeeded.",
listCacheStore.removeElement(listKey, jedis.llen(listKeyStr).intValue()));
Random random = new Random();
final int deletedIndex = random.nextInt(expectedElements.size());
String deletedElement = jedis.lindex(listKeyStr, deletedIndex);
Assert.assertTrue("The operation to be tested failed.",
listCacheStore.removeElement(listKey, deletedIndex));
expectedElements.remove(deletedElement);
Assert.assertEquals("The quantity is not as expected.",
expectedElements.size(), jedis.llen(listKeyStr).intValue());
Set<String> actualElements = getListElements(listKeyStr);
Assert.assertTrue("The added value is not as expected.",
actualElements.containsAll(expectedElements));
}
@Test
public void containsElementTest() {
ListCacheStore<String> listCacheStore = newListCacheStore();
final CacheKey listKey = new CacheKey("list_contains_element");
final String listKeyStr = RedisUtils.toRedisCacheKey(IDENTIFY, listKey);
final Set<String> expectedElements = new HashSet<>();
expectedElements.add("a");
expectedElements.add("b");
expectedElements.add("c");
expectedElements.add("d");
expectedElements.add("e");
final String[] expectedElementsArr = new String[expectedElements.size()];
expectedElements.toArray(expectedElementsArr);
jedis.lpush(listKeyStr, expectedElementsArr);
Set<String> actualElements = getListElements(listKeyStr);
expectedElements.add("f");
expectedElements.add("g");
expectedElements.add("h");
expectedElements.add("i");
for (String expectedElement : expectedElements) {
Assert.assertEquals(String.format("Make a difference: '%s'.", expectedElement),
actualElements.contains(expectedElement),
listCacheStore.containsElement(listKey, expectedElement));
}
}
@Test
public void isEmptyTest() {
ListCacheStore<String> listCacheStore = newListCacheStore();
final CacheKey listKey = new CacheKey("list_is_empty");
final String listKeyStr = RedisUtils.toRedisCacheKey(IDENTIFY, listKey);
Assert.assertEquals("Key does not exist but returns 'true'",
!jedis.exists(listKeyStr), listCacheStore.isEmpty(listKey));
jedis.lpush(listKeyStr, "test");
Assert.assertEquals("Key does exist but returns 'false'.",
jedis.exists(listKeyStr), !listCacheStore.isEmpty(listKey));
}
@Test
public void elementsLengthTest() {
ListCacheStore<String> listCacheStore = newListCacheStore();
final CacheKey listKey = new CacheKey("list_elements_length");
final String listKeyStr = RedisUtils.toRedisCacheKey(IDENTIFY, listKey);
final Set<String> expectedElements = new HashSet<>();
expectedElements.add("a");
expectedElements.add("b");
expectedElements.add("c");
expectedElements.add("d");
expectedElements.add("e");
final String[] expectedElementsArr = new String[expectedElements.size()];
expectedElements.toArray(expectedElementsArr);
long beforeLength = jedis.llen(listKeyStr);
if (jedis.llen(listKeyStr) == 0) {
Assert.assertEquals("The list was empty but returned a value other than '-1' or '0'.",
-1, listCacheStore.elementsLength(listKey));
} else {
Assert.assertEquals("The length of the list is incorrect.",
beforeLength, listCacheStore.elementsLength(listKey));
}
jedis.del(listKeyStr);
jedis.lpush(listKeyStr, expectedElementsArr);
Assert.assertEquals("The length of the list is incorrect.",
jedis.llen(listKeyStr).intValue(), listCacheStore.elementsLength(listKey));
}
@Test
public void clearCollectionTest() {
ListCacheStore<String> listCacheStore = newListCacheStore();
final CacheKey listKey = new CacheKey("list_clear_collection");
final String listKeyStr = RedisUtils.toRedisCacheKey(IDENTIFY, listKey);
final Set<String> expectedElements = new HashSet<>();
expectedElements.add("a");
expectedElements.add("b");
expectedElements.add("c");
expectedElements.add("d");
expectedElements.add("e");
final String[] expectedElementsArr = new String[expectedElements.size()];
expectedElements.toArray(expectedElementsArr);
Assert.assertEquals("The list does not exist but returns 'true'.",
jedis.exists(listKeyStr), listCacheStore.clearCollection(listKey));
jedis.lpush(listKeyStr, expectedElementsArr);
Assert.assertTrue("The operation to be tested failed.",
listCacheStore.clearCollection(listKey));
Assert.assertEquals("The list is not empty after the clear operation",
0, jedis.llen(listKeyStr).intValue());
Assert.assertFalse("The list is still exist", jedis.exists(listKeyStr));
}
@Test
public void getElementTest() {
ListCacheStore<String> listCacheStore = newListCacheStore();
final CacheKey listKey = new CacheKey("list_get_element");
final String listKeyStr = RedisUtils.toRedisCacheKey(IDENTIFY, listKey);
final List<String> expectedElements = new ArrayList<>();
expectedElements.add("a");
expectedElements.add("b");
expectedElements.add("c");
expectedElements.add("d");
expectedElements.add("e");
final String[] expectedElementsArr = new String[expectedElements.size()];
expectedElements.toArray(expectedElementsArr);
jedis.lpush(listKeyStr, expectedElementsArr);
Collections.reverse(expectedElements);
for (int i = 0; i < expectedElements.size(); i++) {
Assert.assertEquals("Wrong value, index: " + i, expectedElements.get(i),
listCacheStore.getElement(listKey, i));
}
}
@Test
public void getElementsByRangeTest() {
ListCacheStore<String> listCacheStore = newListCacheStore();
final CacheKey listKey = new CacheKey("list_get_elements_by_range");
final String listKeyStr = RedisUtils.toRedisCacheKey(IDENTIFY, listKey);
final List<String> expectedElements = new ArrayList<>();
expectedElements.add("a");
expectedElements.add("b");
expectedElements.add("c");
expectedElements.add("d");
expectedElements.add("e");
expectedElements.add("f");
expectedElements.add("g");
expectedElements.add("h");
expectedElements.add("i");
expectedElements.add("j");
expectedElements.add("k");
final String[] expectedElementsArr = new String[expectedElements.size()];
expectedElements.toArray(expectedElementsArr);
jedis.lpush(listKeyStr, expectedElementsArr);
Collections.reverse(expectedElements);
final int start = 2;
final int length = 4;
List<String> actualElements = listCacheStore.getElementsByRange(listKey, start, length);
for (int i = 0; i < length; i++) {
Assert.assertEquals("Wrong value, index: " + i,
expectedElements.get(start + i), actualElements.get(i));
}
}
}
@@ -0,0 +1,414 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.redis;
import com.google.common.base.Throwables;
import net.lamgc.cgj.bot.cache.CacheKey;
import net.lamgc.cgj.bot.cache.MapCacheStore;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import net.lamgc.cgj.bot.cache.convert.StringToStringConverter;
import org.junit.*;
import org.junit.rules.TemporaryFolder;
import redis.clients.jedis.Jedis;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import static net.lamgc.cgj.bot.cache.redis.util.RedisTestUtils.assertDeleteIfExist;
import static net.lamgc.cgj.bot.cache.redis.util.RedisTestUtils.randomString;
/**
* @see RedisMapCacheStore
*/
public class RedisMapCacheStoreTest {
private final static TemporaryFolder tempFolder = TemporaryFolder.builder().build();
private final static String KEY_PREFIX = "test:map";
private final static StringConverter<String> CONVERTER = new StringToStringConverter();
private final static RedisCacheStoreFactory factory = new RedisCacheStoreFactory();
private static MapCacheStore<String> cacheStore;
private static Jedis jedis;
@BeforeClass
public static void beforeAll() {
jedis = new Jedis();
try {
tempFolder.create();
} catch (IOException e) {
Assert.fail(Throwables.getStackTraceAsString(e));
return;
}
try {
factory.initial(tempFolder.newFolder("Redis"));
cacheStore =
factory.newMapCacheStore(KEY_PREFIX, CONVERTER);
} catch (IOException e) {
Assert.fail(Throwables.getStackTraceAsString(e));
}
}
@AfterClass
public static void afterAll() {
if (jedis != null) {
jedis.close();
}
}
@Before
public void before() {
Assert.assertTrue("Clear execution failed before the test started.", cacheStore.clear());
}
@After
public void after() {
Assert.assertTrue("After the test, clear execution failed", cacheStore.clear());
}
@Test(expected = IllegalArgumentException.class)
public void emptyPrefixTest() {
factory.newMapCacheStore("", CONVERTER);
}
@Test(expected = NullPointerException.class)
public void nullPrefixTest() {
factory.newMapCacheStore(null, CONVERTER);
}
@Test
public void prefixCheck() throws NoSuchFieldException, IllegalAccessException {
final Field prefixField = RedisMapCacheStore.class.getDeclaredField("keyPrefix");
prefixField.setAccessible(true);
String prefix = (String) prefixField.get(factory.newMapCacheStore(KEY_PREFIX, CONVERTER));
Assert.assertTrue("The prefix does not contain a separator at the end.",
prefix.endsWith(RedisUtils.KEY_SEPARATOR));
prefix = (String) prefixField.get(factory.newMapCacheStore(
KEY_PREFIX + RedisUtils.KEY_SEPARATOR, CONVERTER));
Assert.assertTrue("The separator at the end of the prefix is missing.",
prefix.endsWith(RedisUtils.KEY_SEPARATOR));
prefixField.setAccessible(false);
}
@Test
public void nullThrowTest() {
final CacheKey key = new CacheKey("test_null_throw");
Assert.assertThrows(NullPointerException.class, () -> cacheStore.mapSize(null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.mapFieldSet(null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.mapValueSet(null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.put(null, "field", "value"));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.put(key, null, "value"));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.put(key, "field", null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.putAll(null, new HashMap<>()));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.putAll(key, null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.putIfNotExist(null, "field", "value"));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.putIfNotExist(key, null, "value"));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.putIfNotExist(key, "field", null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.get(key, null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.get(null, "field"));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.removeField(key, null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.removeField(null, "field"));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.mapIsEmpty(null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.clearMap(null));
}
@Test
public void putTest() {
final CacheKey key = new CacheKey("test_put");
final String keyString = RedisUtils.toRedisCacheKey(KEY_PREFIX, key);
final String field = "field01";
final String value = "value01";
if (jedis.hexists(keyString, field)) {
Assert.assertEquals("The field used by the test is occupied and deletion failed.",
1, jedis.hdel(keyString, field).intValue());
}
Assert.assertTrue("The operation to be tested failed.", cacheStore.put(key, field, value));
Assert.assertTrue("The field does not exist after put.", jedis.hexists(keyString, field));
Assert.assertEquals("The value of the field changes after put.", value, jedis.hget(keyString, field));
}
@Test
public void getTest() {
final CacheKey key = new CacheKey("test_get");
final String keyString = RedisUtils.toRedisCacheKey(KEY_PREFIX, key);
final String field = "field01";
final String value = "value01";
assertDeleteIfExist(jedis, keyString);
Assert.assertNull("Method returned a non null value for a field that does not exist.",
cacheStore.get(key, field));
Assert.assertTrue("Failed to set the field used by the test.",
jedis.hset(keyString, field, value).intValue() >= 0);
Assert.assertTrue("The field used by the test does not exist.",
jedis.hexists(keyString, field));
Assert.assertEquals("The obtained field value does not match the expected value.",
value, cacheStore.get(key, field));
}
@Test
public void putAllTest() {
final CacheKey key = new CacheKey("test_put_all");
final String keyString = RedisUtils.toRedisCacheKey(KEY_PREFIX, key);
final Map<String, String> expectMap = new HashMap<>();
expectMap.put("field01", "value01");
expectMap.put("field02", "value02");
expectMap.put("field03", "value03");
expectMap.put("field04", "value04");
expectMap.put("field05", "value05");
expectMap.put("field06", "value06");
expectMap.put("field07", "value07");
assertDeleteIfExist(jedis, keyString);
Assert.assertEquals("The key does not exist, but the empty collection was added successfully.",
jedis.exists(keyString),
cacheStore.putAll(key, Collections.emptyMap()));
Assert.assertTrue("The operation to be tested failed.", cacheStore.putAll(key, expectMap));
Map<String, String> actualMap = jedis.hgetAll(keyString);
for (String actualKey : actualMap.keySet()) {
Assert.assertTrue("Field does not exist in the expected.", expectMap.containsKey(actualKey));
Assert.assertEquals("The value of field " + actualKey + " does not match the expected value.",
expectMap.get(actualKey), actualMap.get(actualKey));
}
Assert.assertEquals("Key does not exist, but adding empty collection failed.",
jedis.exists(keyString),
cacheStore.putAll(key, Collections.emptyMap()));
}
@Test
public void putIfNotExistTest() {
final CacheKey key = new CacheKey("test_put_if_not_exist");
final String keyString = RedisUtils.toRedisCacheKey(KEY_PREFIX, key);
final String field = "field01";
final String value = "value01";
if (jedis.hexists(keyString, field)) {
Assert.assertEquals("The field used by the test is occupied and deletion failed.",
1, jedis.hdel(keyString, field).intValue());
}
Assert.assertTrue("Field does not exist but put failed.", cacheStore.putIfNotExist(key, field, value));
Assert.assertFalse("Field does exist but put successful.", cacheStore.putIfNotExist(key, field, value));
}
@Test
public void mapSizeTest() {
final CacheKey key = new CacheKey("test_map_size");
final String keyString = RedisUtils.toRedisCacheKey(KEY_PREFIX, key);
final Map<String, String> expectMap = new HashMap<>();
expectMap.put("field01", "value01");
expectMap.put("field02", "value02");
expectMap.put("field03", "value03");
expectMap.put("field04", "value04");
expectMap.put("field05", "value05");
expectMap.put("field06", "value06");
expectMap.put("field07", "value07");
assertDeleteIfExist(jedis, keyString);
Assert.assertEquals("Non 0 returned when map does not exist.", 0, cacheStore.mapSize(key));
Assert.assertEquals("The number of test fields prepared is inconsistent.",
expectMap.size(), jedis.hset(keyString, expectMap).intValue());
Assert.assertEquals("The number of fields obtained does not match the actual number.",
expectMap.size(), cacheStore.mapSize(key));
}
@Test
public void mapIsEmptyTest() {
final CacheKey key = new CacheKey("test_put_if_not_exist");
final String keyString = RedisUtils.toRedisCacheKey(KEY_PREFIX, key);
final String field = "field01";
final String value = "value01";
if (jedis.hlen(keyString) != 0) {
Assert.assertFalse("Map exists but returns 'true'.", cacheStore.mapIsEmpty(key));
Assert.assertEquals("Failed to delete map.", 1, jedis.del(keyString).intValue());
Assert.assertTrue("Map not exists but returns 'false'.", cacheStore.mapIsEmpty(key));
} else {
Assert.assertTrue("Map not exists but returns 'false'.", cacheStore.mapIsEmpty(key));
Assert.assertEquals("Failed to set field value for test.",
1, jedis.hset(keyString, field, value).intValue());
Assert.assertFalse("Map exists but returns 'true'.", cacheStore.mapIsEmpty(key));
}
}
@Test
public void mapFieldSetTest() {
final CacheKey key = new CacheKey("test_map_field_set");
final String keyString = RedisUtils.toRedisCacheKey(KEY_PREFIX, key);
final Map<String, String> expectMap = new HashMap<>();
expectMap.put("field01", "value01");
expectMap.put("field02", "value02");
expectMap.put("field03", "value03");
expectMap.put("field04", "value04");
expectMap.put("field05", "value05");
expectMap.put("field06", "value06");
expectMap.put("field07", "value07");
assertDeleteIfExist(jedis, keyString);
Assert.assertNull("The mapFieldSet() method returned a non null value for a nonexistent map." +
"(If the map does not exist, null should be returned instead of an empty set)",
cacheStore.mapFieldSet(key));
Assert.assertEquals("The number of test fields prepared is inconsistent.",
expectMap.size(), jedis.hset(keyString, expectMap).intValue());
Set<String> fieldSet = cacheStore.mapFieldSet(key);
Assert.assertNotNull("Method returns 'null' for the existing map.", fieldSet);
Assert.assertTrue("The actual set is different from the expectation.",
fieldSet.containsAll(expectMap.keySet()));
Assert.assertTrue("The actual set is different from the expectation.",
expectMap.keySet().containsAll(fieldSet));
}
@Test
public void mapValueSetTest() {
final CacheKey key = new CacheKey("test_map_value_set");
final String keyString = RedisUtils.toRedisCacheKey(KEY_PREFIX, key);
final Map<String, String> expectMap = new HashMap<>();
expectMap.put("field01", "value01");
expectMap.put("field02", "value02");
expectMap.put("field03", "value03");
expectMap.put("field04", "value04");
expectMap.put("field05", "value05");
expectMap.put("field06", "value06");
expectMap.put("field07", "value07");
assertDeleteIfExist(jedis, keyString);
Assert.assertNull("The mapValueSet() method returned a non null value for a nonexistent map." +
"(If the map does not exist, null should be returned instead of an empty set)",
cacheStore.mapValueSet(key));
Assert.assertEquals("The number of test fields prepared is inconsistent.",
expectMap.size(), jedis.hset(keyString, expectMap).intValue());
Set<String> valueSet = cacheStore.mapValueSet(key);
Assert.assertNotNull("Method returns 'null' for the existing map.", valueSet);
Assert.assertTrue("The actual set is different from the expectation.",
valueSet.containsAll(expectMap.values()));
Assert.assertTrue("The actual set is different from the expectation.",
expectMap.values().containsAll(valueSet));
}
@Test
public void removeFieldTest() {
final CacheKey key = new CacheKey("test_remove_field");
final String keyString = RedisUtils.toRedisCacheKey(KEY_PREFIX, key);
final String field = "field";
final String value = "value";
final Map<String, String> expectMap = new HashMap<>();
expectMap.put("field01", "value01");
expectMap.put("field02", "value02");
expectMap.put("field03", "value03");
expectMap.put("field04", "value04");
expectMap.put("field05", "value05");
expectMap.put("field06", "value06");
expectMap.put("field07", "value07");
assertDeleteIfExist(jedis, keyString);
Assert.assertFalse("Returns 'true' when trying to delete a field in a nonexistent map.",
cacheStore.removeField(key, "field"));
Assert.assertEquals("Failed to add field for test.",
1, jedis.hset(keyString, field, value).intValue());
Assert.assertFalse("Returns 'true' when trying to delete a field that does not exist in the map.",
cacheStore.removeField(key, randomString(10)));
assertDeleteIfExist(jedis, keyString);
Assert.assertEquals("Failed to add field for test.",
expectMap.size(), jedis.hset(keyString, expectMap).intValue());
for (String expectField : expectMap.keySet()) {
Assert.assertTrue("The attempt to delete an existing field failed: '" + expectField + "'.",
cacheStore.removeField(key, expectField));
}
}
@Test
public void containsFieldTest() {
final CacheKey key = new CacheKey("test_contains_field");
final String keyString = RedisUtils.toRedisCacheKey(KEY_PREFIX, key);
final String field = "field";
final String value = "value";
assertDeleteIfExist(jedis, keyString);
Assert.assertFalse("Returns 'true' when trying to check for a map that does not exist.",
cacheStore.containsField(key, "field"));
Assert.assertEquals("Failed to add field for test.",
1, jedis.hset(keyString, field, value).intValue());
Assert.assertFalse("Returns 'true' when trying to check for a field that does not exist.",
cacheStore.removeField(key, randomString(10)));
Assert.assertTrue("An attempt to check for existing fields returned 'false'.",
cacheStore.containsField(key, field));
}
@Test
public void clearMapTest() {
final CacheKey key = new CacheKey("test_remove_field");
final String keyString = RedisUtils.toRedisCacheKey(KEY_PREFIX, key);
final Map<String, String> expectMap = new HashMap<>();
expectMap.put("field01", "value01");
expectMap.put("field02", "value02");
expectMap.put("field03", "value03");
expectMap.put("field04", "value04");
expectMap.put("field05", "value05");
expectMap.put("field06", "value06");
expectMap.put("field07", "value07");
assertDeleteIfExist(jedis, keyString);
Assert.assertFalse("Attempt to empty nonexistent map succeeded.", cacheStore.clearMap(key));
Assert.assertEquals("Failed to add field for test.",
expectMap.size(), jedis.hset(keyString, expectMap).intValue());
Assert.assertTrue("The operation to be tested failed.", cacheStore.clearMap(key));
Assert.assertEquals("After the clear operation, the map still contains fields.",
0, jedis.hlen(keyString).intValue());
}
}
@@ -0,0 +1,142 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.redis;
import net.lamgc.cgj.bot.cache.CacheKey;
import net.lamgc.cgj.bot.cache.SingleCacheStore;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import net.lamgc.cgj.bot.cache.convert.StringToStringConverter;
import org.junit.*;
import redis.clients.jedis.Jedis;
import java.lang.reflect.Field;
import static net.lamgc.cgj.bot.cache.redis.util.RedisTestUtils.*;
/**
* @see RedisCacheStore
* @see RedisSingleCacheStore
*/
public class RedisSingleCacheStoreTest {
private final static String KEY_PREFIX = "test:single";
private final static StringConverter<String> CONVERTER = new StringToStringConverter();
private static Jedis jedis;
private final RedisConnectionPool connectionPool = new RedisConnectionPool();
private final SingleCacheStore<String> cacheStore =
new RedisSingleCacheStore<>(connectionPool, KEY_PREFIX, CONVERTER);
@BeforeClass
public static void beforeAllTest() {
jedis = new Jedis();
}
@AfterClass
public static void afterAllTest() {
if (jedis != null && jedis.isConnected()) {
jedis.close();
}
}
@Before
public void before() {
Assert.assertTrue("Clear execution failed before the test started.", cacheStore.clear());
}
@After
public void after() {
Assert.assertTrue("After the test, clear execution failed", cacheStore.clear());
}
@Test(expected = IllegalArgumentException.class)
public void emptyPrefixTest() {
new RedisSingleCacheStore<>(connectionPool, "", CONVERTER);
}
@Test(expected = NullPointerException.class)
public void nullPrefixTest() {
new RedisSingleCacheStore<>(connectionPool, null, CONVERTER);
}
@Test
public void prefixCheck() throws NoSuchFieldException, IllegalAccessException {
final Field prefixField = RedisSingleCacheStore.class.getDeclaredField("keyPrefix");
prefixField.setAccessible(true);
String prefix = (String) prefixField.get(new RedisSingleCacheStore<>(connectionPool, KEY_PREFIX, CONVERTER));
Assert.assertTrue("The prefix does not contain a separator at the end.",
prefix.endsWith(RedisUtils.KEY_SEPARATOR));
prefix = (String) prefixField.get(new RedisSingleCacheStore<>(connectionPool,
KEY_PREFIX + RedisUtils.KEY_SEPARATOR, CONVERTER));
Assert.assertTrue("The separator at the end of the prefix is missing.",
prefix.endsWith(RedisUtils.KEY_SEPARATOR));
prefixField.setAccessible(false);
}
@Test
public void setTest() {
final CacheKey key = new CacheKey("test_set");
final String keyString = RedisUtils.toRedisCacheKey(KEY_PREFIX, key);
String value = randomString(10);
assertDeleteIfExist(jedis, keyString);
Assert.assertTrue("Failed to set value on specified key.", cacheStore.set(key, value));
Assert.assertEquals("The value set does not match the expected value.",
value, jedis.get(keyString));
value = randomString(12);
Assert.assertTrue("Cannot rewritten the value of an existing key.", cacheStore.set(key, value));
Assert.assertEquals("The rewritten value does not match the expected value.",
value, jedis.get(keyString));
}
@Test
public void setIfNotExistTest() {
final CacheKey key = new CacheKey("test_set_if_not_exist");
final String keyString = RedisUtils.toRedisCacheKey(KEY_PREFIX, key);
String value = randomString(10);
assertDeleteIfExist(jedis, keyString);
Assert.assertTrue("Failed to set value on specified key.", cacheStore.setIfNotExist(key, value));
Assert.assertEquals("The value set does not match the expected value.",
value, jedis.get(keyString));
value = randomString(12);
Assert.assertFalse("Write value to existing key succeeded.", cacheStore.setIfNotExist(key, value));
Assert.assertNotEquals("The key value is modified and the method setIfNotExist() returns 'false'.",
value, jedis.get(keyString));
}
@Test
public void getTest() {
final CacheKey key = new CacheKey("test_get");
final String keyString = RedisUtils.toRedisCacheKey(KEY_PREFIX, key);
String value = randomString(10);
assertDeleteIfExist(jedis, keyString);
Assert.assertNull("The get() method returned a non null value for a nonexistent key.",
cacheStore.get(key));
Assert.assertTrue("Failed to set test key.", RedisUtils.isOk(jedis.set(keyString, value)));
Assert.assertEquals("The value obtained does not match the expected value.",
value, cacheStore.get(key));
}
}
@@ -0,0 +1,45 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.redis;
import net.lamgc.cgj.bot.cache.CacheKey;
import org.junit.Assert;
import org.junit.Test;
/**
* @see RedisUtils
*/
public class RedisUtilsTest {
@Test
public void isOkTest() {
Assert.assertTrue(RedisUtils.isOk("OK"));
Assert.assertTrue(RedisUtils.isOk("ok"));
Assert.assertFalse(RedisUtils.isOk("Failed"));
}
@Test
public void toRedisCacheKey() {
final CacheKey key = new CacheKey("test");
final String prefix = "prefix";
Assert.assertEquals("prefix:test", RedisUtils.toRedisCacheKey(prefix, key));
Assert.assertEquals("prefix:test", RedisUtils.toRedisCacheKey(prefix + RedisUtils.KEY_SEPARATOR, key));
}
}
@@ -0,0 +1,34 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.redis;
public class SimpleRedisCacheStore extends RedisCacheStore<String> {
private final String keyPrefix;
protected SimpleRedisCacheStore(RedisConnectionPool connectionPool, String keyPrefix) {
super(connectionPool);
this.keyPrefix = keyPrefix;
}
@Override
protected String getKeyPrefix() {
return keyPrefix;
}
}
@@ -0,0 +1,95 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.redis.util;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import net.lamgc.cgj.bot.cache.redis.RedisConnectionProperties;
import net.lamgc.cgj.bot.cache.redis.RedisUtils;
import org.junit.Assert;
import redis.clients.jedis.Jedis;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.util.Random;
/**
*
* @author LamGC
*/
public final class RedisTestUtils {
private RedisTestUtils() {}
private final static Gson GSON_INSTANCE = new GsonBuilder()
.serializeNulls()
.setPrettyPrinting()
.create();
/**
* 创建测试用配置文件.
* @param testDirectory 组件文件夹.
* @param properties 配置对象.
* @throws IOException 当配置文件写入失败时抛出.
*/
public static void createConnectionProperties(File testDirectory, RedisConnectionProperties properties)
throws IOException {
File propertiesFile = new File(testDirectory, "Redis/" + RedisUtils.PROPERTIES_FILE_NAME);
Files.write(propertiesFile.toPath(),
GSON_INSTANCE.toJson(properties).getBytes(StandardCharsets.UTF_8),
StandardOpenOption.CREATE);
}
/**
* 如果存在键, 则删除, 如果失败则判定断言失败.
* @param jedis Jedis 对象.
* @param keyString 待删除的键名.
*/
public static void assertDeleteIfExist(Jedis jedis, String keyString) {
if (jedis.exists(keyString)) {
Assert.assertEquals("The key used by the test is occupied and deletion failed.",
1, jedis.del(keyString).intValue());
}
}
/**
* 随机字符串.
* @param length 长度.
* @return 返回指定长度的随机字符串.
*/
public static String randomString(int length) {
final char[] chars = new char[] {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
};
StringBuilder buffer = new StringBuilder(length);
Random random = new Random();
for (int i = 0; i < length; i++) {
buffer.append(chars[random.nextInt(chars.length)]);
}
return buffer.toString();
}
}
+40
View File
@@ -0,0 +1,40 @@
# Event 模块 #
Event 模块用于定义 ContentGrabbingJi 所使用事件系统的规范。
通过定义事件系统的规范接口,可增加项目模块配置的灵活性。
虽说如此,但 Event 模块并未计划允许用户任意更换,仅允许于开发阶段更换。
## 组件 ##
### EventExecutor 事件执行器
事件执行器用于接收任意事件,并将事件交给可处理该事件的对象方法。
根据实现不同,事件执行器可能是同步的,也可能是异步的,需检查 `isAsync` 方法返回值以了解相关信息。
### EventObject 事件对象
当某一对象实现 `EventObject` 时,对象即为事件对象,
事件对象可交由事件执行器分发到处理方法中。
### HandlerRegistry 事件处理注册器
用于注册并存储所有 EventHandler 内符合条件的处理方法。
在 EventExecutor 需要时将会请求可处理事件的所有处理方法。
### EventHandler 事件处理器
当某一对象实现 `EventHandler` 时,则表明该对象包含用于处理指定事件的处理方法。
HandlerRegistry 将会扫描内部方法,并将符合要求的方法注册为处理方法。
### EventObject 事件对象
事件对象接口,实现了该接口的对象将视为一种事件对象。
事件对象可通过 EventExecutor 投递到任何可处理该事件的处理方法进行处理。
### AbstractEventObject 抽象事件对象
实现了 EventObject 中 `getEventId` 方法的抽象对象。
### Cancelable 可取消事件
当事件实现了该接口时,代表事件可以被取消。
当事件被取消后,EventExecutor 将会终止处理该事件,同时事件将通知所有已注册的 Observer。
### SupportedCancel 支持取消接口
当 EventExecutor 实现该接口时,表明 EventExecutor 拥有在事件取消时终止事件的能力,
需要注意的是,即使 EventExecutor 未实现该接口,也不一定代表 EventExecutor 不支持终止事件的处理。
## 相关实现 ##
目前已有默认的实现存在于 Core 模块中,目前暂不需要开发其他实现。
+32
View File
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (C) 2021 LamGC
~
~ ContentGrabbingJi is free software: you can redistribute it and/or modify
~ it under the terms of the GNU Affero General Public License as
~ published by the Free Software Foundation, either version 3 of the
~ License.
~
~ ContentGrabbingJi is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU Affero General Public License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ContentGrabbingJi</artifactId>
<groupId>net.lamgc</groupId>
<version>3.0.0-alpha-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ContentGrabbingJi-Event-api</artifactId>
</project>
@@ -0,0 +1,36 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.event;
import java.util.UUID;
/**
* 抽象事件对象.
* <p> 已完成对 {@link EventObject#getEventId()} 的实现.
* @author LamGC
*/
public abstract class AbstractEventObject implements EventObject {
private final UUID eventId = UUID.randomUUID();
@Override
public UUID getEventId() {
return eventId;
}
}
@@ -0,0 +1,66 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.event;
import java.util.Observer;
/**
* 可取消接口.
* <p> 实现了该接口的事件可对其处理进行取消.
* <p> 注意: 取消操作是不可逆的!
* @author LamGC
*/
public interface Cancelable {
/**
* 取消该事件.
* <p> 该操作不可逆.
* <p> 调用本方法后, 将会触发所有已通过 {@link #registerCancelObserver(Observer)} 注册的 {@link Observer} 事件取消的状态变更.
* @see #registerCancelObserver(Observer)
*/
void cancel();
/**
* 检查事件是否已被取消.
* <p> 注意: 本方法不同于 {@link Thread#interrupted()} 方法, 即使调用本方法, 也不会清除取消状态(取消操作是不可逆的).
* @return 如果事件已被取消, 返回 true.
*/
boolean canceled();
/**
* 注册事件取消监听器.
* <p> 注意: 如果发生取消事件, 无论 {@link java.util.Observable} 是否为事件对象本身, 参数 {@code arg} 都必须传递事件对象自身!
* @param cancelObserver 观察者对象.
* @throws UnsupportedOperationException 当该可取消对象不支持 {@link java.util.Observable} 时抛出,
* 既然不支持观察取消事件, 那么 {@link #observableCancel()} 应当返回 {@code false}, 否则该方法不允许抛出该异常.
*/
default void registerCancelObserver(Observer cancelObserver) throws UnsupportedOperationException {
throw new UnsupportedOperationException("This operation is not supported by this event.");
}
/**
* 是否可观察取消事件.
* <p> 如果本方法返回 {@code true},
* 那么 {@link #registerCancelObserver(Observer)} 不允许抛出 {@link UnsupportedOperationException} 异常.
* @return 如果可以, 返回 true.
*/
default boolean observableCancel() {
return false;
}
}
@@ -0,0 +1,38 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.event;
/**
* 事件执行器.
* @author LamGC
*/
public interface EventExecutor {
/**
* 执行事件.
* @param event 事件对象.
*/
void execute(EventObject event);
/**
* 是否为异步事件执行器.
* @return 如果为异步事件执行器, 返回 {@code true}, 如果为同步事件执行器, 返回 {@code false}.
*/
boolean isAsync();
}
@@ -0,0 +1,45 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.event;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 事件处理器注解.
* <p> 标记了该注解的方法, 如符合处理方法条件, 则会被 {@link HandlerRegistry} 注册为事件处理方法.
* @author LamGC
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EventHandler {
/**
* 是否接受事件对象的子类.
* <p> 当该选项为 {@code true} 时, 子类事件也会传递到该方法进行处理.
* 例如事件 Parent, 和它的子类 Child, 处理方法: {@code handler(Parent event)};
* 如果该选项为 {@code true}, 那么 Child 也会传递到该方法进行处理, 反之, 如果该选项为 {@code false},
* 那么该方法只会接收 Parent 事件, 而不会接收它的子类 Child.
* <p> 默认值: {@code false}
* @return 返回该方法是否支持继承性.
*/
boolean inheritable() default false;
}
@@ -0,0 +1,35 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.event;
import java.util.UUID;
/**
* 事件对象.
* @author LamGC
*/
public interface EventObject {
/**
* 获取事件 UUID.
* <p> 需保证 UUID 对应了唯一的事件对象, 即使内容重复.
* @return 返回事件Id.
*/
UUID getEventId();
}
@@ -0,0 +1,62 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.event;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
/**
* 事件处理工具类.
* @author LamGC
*/
public final class EventUtils {
private EventUtils() {}
/**
* 检查 {@link EventExecutor} 是否支持取消事件.
* @param executor 事件执行器.
* @return 如果支持, 返回 {@code true}
*/
public static boolean isSupportedCancel(EventExecutor executor) {
return executor instanceof SupportedCancel;
}
/**
* 检查方法是否符合事件处理方法条件.
* @param method 待检查的方法.
* @return 如果符合, 返回 true.
*/
public static boolean checkEventHandlerMethod(Method method) {
int modifiers = method.getModifiers();
// 新版事件系统将不再允许静态方法作为事件处理方法, 以降低管理难度.
if (!Modifier.isPublic(modifiers) || Modifier.isAbstract(modifiers) || Modifier.isStatic(modifiers)) {
return false;
}
if (!method.isAnnotationPresent(EventHandler.class) || method.getParameterCount() != 1) {
return false;
}
Class<?> param = method.getParameterTypes()[0];
if (!EventObject.class.isAssignableFrom(param)) {
return false;
}
return method.getReturnType().equals(Void.TYPE);
}
}
@@ -0,0 +1,44 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.event;
import java.lang.reflect.Method;
import java.util.Map;
/**
* 事件处理注册器.
* <p> 实现了该接口的类将允许注册事件处理方法.
* @author LamGC
*/
public interface HandlerRegistry {
/**
* 注册对象中的事件处理方法.
* @param handlerObject 包含事件处理方法的对象.\
* @return 返回已成功添加的方法数.
*/
int registerHandler(Object handlerObject);
/**
* 获取能处理指定事件的所有方法.
* @param event 待匹配的事件对象.
* @return 返回一个集合, 集合存储了可处理该事件的所有方法和它所属类的对象.
*/
Map<Method, Object> getMatchedHandlerMethod(EventObject event);
}
@@ -0,0 +1,57 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.event;
import java.util.NoSuchElementException;
import java.util.UUID;
/**
* 支持取消接口.
* <p> 实现了该接口的 {@link EventExecutor} 可对已投递执行的事件进行取消, 以阻止事件被执行.
* @author LamGC
*/
public interface SupportedCancel {
/**
* 通过 EventId 取消事件的处理.
* @param eventId 事件Id.
* @return 如果成功, 返回 true, 如果事件已执行完成, 返回 false.
* @throws UnsupportedOperationException 当事件未实现 {@link Cancelable} 接口时抛出.
* @throws NoSuchElementException 当 EventId 所属事件在 {@link SupportedCancel} 中无法找到时抛出.
*/
boolean cancelEvent(UUID eventId) throws UnsupportedOperationException, NoSuchElementException;
/**
* 通过 Event 对象取消事件的处理.
* @param event 事件对象.
* @return 如果成功, 返回 true, 如果事件已执行完成, 返回 false.
* @throws UnsupportedOperationException 当事件未实现 {@link Cancelable} 接口时抛出.
* @throws NoSuchElementException 当事件在 {@link SupportedCancel} 中无法找到时抛出.
*/
boolean cancelEvent(EventObject event) throws UnsupportedOperationException, NoSuchElementException;
/**
* 对可取消对象执行取消处理操作.
* @param cancelableEvent 可取消对象事件.
* @return 如果成功, 返回 true, 如果事件已执行完成, 返回 false.
* @throws NoSuchElementException 当事件在 {@link SupportedCancel} 中无法找到时抛出.
* @throws IllegalArgumentException 当 Cancelable 对象未实现 {@link EventObject} (即不是一个事件对象) 时抛出.
*/
boolean cancelEvent(Cancelable cancelableEvent) throws NoSuchElementException, IllegalArgumentException;
}
@@ -0,0 +1,93 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.event;
import net.lamgc.cgj.bot.event.handler.IllegalHandler;
import net.lamgc.cgj.bot.event.handler.StandardHandler;
import net.lamgc.cgj.bot.event.object.TestEvent;
import org.junit.Assert;
import org.junit.Test;
import java.lang.reflect.Method;
import java.util.NoSuchElementException;
import java.util.UUID;
/**
* @see EventUtils
*/
public class EventUtilsTest {
@Test
public void supportedCancelCheckTest() {
class NonSupportedCancelClass implements EventExecutor {
@Override
public void execute(EventObject event) {
}
@Override
public boolean isAsync() {
return false;
}
}
class SupportedCancelClass implements SupportedCancel, EventExecutor {
@Override
public boolean cancelEvent(UUID eventId) throws UnsupportedOperationException, NoSuchElementException {
return false;
}
@Override
public boolean cancelEvent(EventObject event) throws UnsupportedOperationException, NoSuchElementException {
return false;
}
@Override
public boolean cancelEvent(Cancelable cancelableEvent) throws NoSuchElementException {
return false;
}
@Override
public void execute(EventObject event) {
}
@Override
public boolean isAsync() {
return false;
}
}
Assert.assertTrue(EventUtils.isSupportedCancel(new SupportedCancelClass()));
Assert.assertFalse(EventUtils.isSupportedCancel(new NonSupportedCancelClass()));
}
@Test
public void standardHandlerMethodCheckTest() throws NoSuchMethodException {
Method targetMethod = StandardHandler.class.getMethod("standardHandle", TestEvent.class);
Assert.assertTrue(EventUtils.checkEventHandlerMethod(targetMethod));
}
@Test
public void invalidHandlerMethodCheckTest() {
for (Method method : IllegalHandler.class.getDeclaredMethods()) {
Assert.assertFalse(EventUtils.checkEventHandlerMethod(method));
}
}
}
@@ -0,0 +1,62 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.event.handler;
import net.lamgc.cgj.bot.event.EventHandler;
import net.lamgc.cgj.bot.event.object.TestEvent;
public abstract class IllegalHandler {
@EventHandler
public void nonArgumentHandleMethod() {
}
@EventHandler
public abstract void abstractHandleMethod(TestEvent event);
@EventHandler
public static void staticHandleMethod(TestEvent event) {
}
@EventHandler
private void privateHandleMethod(TestEvent event) {
}
public void nonAnnotationHandle() {
}
@EventHandler
public void multiArgumentsHandleMethod(TestEvent event, Object object) {
}
@EventHandler
public void invalidArgumentMethod(Object object) {
}
@EventHandler
public Object invalidReturnTypeMethod(TestEvent event) {
return null;
}
}
@@ -0,0 +1,30 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.event.handler;
import net.lamgc.cgj.bot.event.EventHandler;
import net.lamgc.cgj.bot.event.object.TestEvent;
public class StandardHandler {
@EventHandler
public void standardHandle(TestEvent event) {
}
}
@@ -0,0 +1,33 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.event.object;
import net.lamgc.cgj.bot.event.AbstractEventObject;
public class TestEvent extends AbstractEventObject {
private final String content;
public TestEvent(String content) {
this.content = content;
}
public String getContent() {
return content;
}
}
+32
View File
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (C) 2021 LamGC
~
~ ContentGrabbingJi is free software: you can redistribute it and/or modify
~ it under the terms of the GNU Affero General Public License as
~ published by the Free Software Foundation, either version 3 of the
~ License.
~
~ ContentGrabbingJi is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU Affero General Public License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ContentGrabbingJi</artifactId>
<groupId>net.lamgc</groupId>
<version>3.0.0-alpha-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ContentGrabbingJi-Function-api</artifactId>
</project>
+45
View File
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (C) 2021 LamGC
~
~ ContentGrabbingJi is free software: you can redistribute it and/or modify
~ it under the terms of the GNU Affero General Public License as
~ published by the Free Software Foundation, either version 3 of the
~ License.
~
~ ContentGrabbingJi is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU Affero General Public License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ContentGrabbingJi</artifactId>
<groupId>net.lamgc</groupId>
<version>3.0.0-alpha-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ContentGrabbingJi-Function-pixiv</artifactId>
<dependencies>
<dependency>
<groupId>net.lamgc</groupId>
<artifactId>ContentGrabbingJi-Function-api</artifactId>
<version>3.0.0-alpha-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>net.lamgc</groupId>
<artifactId>ContentGrabbingJi-pixiv</artifactId>
<version>3.0.0-alpha-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (C) 2021 LamGC
~
~ ContentGrabbingJi is free software: you can redistribute it and/or modify
~ it under the terms of the GNU Affero General Public License as
~ published by the Free Software Foundation, either version 3 of the
~ License.
~
~ ContentGrabbingJi is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU Affero General Public License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ContentGrabbingJi</artifactId>
<groupId>net.lamgc</groupId>
<version>3.0.0-alpha-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ContentGrabbingJi-Template-Velocity</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>net.lamgc</groupId>
<artifactId>ContentGrabbingJi-Template-api</artifactId>
<version>3.0.0-alpha-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.2</version>
</dependency>
</dependencies>
</project>
+46
View File
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (C) 2021 LamGC
~
~ ContentGrabbingJi is free software: you can redistribute it and/or modify
~ it under the terms of the GNU Affero General Public License as
~ published by the Free Software Foundation, either version 3 of the
~ License.
~
~ ContentGrabbingJi is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU Affero General Public License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ContentGrabbingJi</artifactId>
<groupId>net.lamgc</groupId>
<version>3.0.0-alpha-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ContentGrabbingJi-Template-api</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>net.lamgc</groupId>
<artifactId>ContentGrabbingJi-framework-api</artifactId>
<version>3.0.0-alpha-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
+39
View File
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (C) 2021 LamGC
~
~ ContentGrabbingJi is free software: you can redistribute it and/or modify
~ it under the terms of the GNU Affero General Public License as
~ published by the Free Software Foundation, either version 3 of the
~ License.
~
~ ContentGrabbingJi is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU Affero General Public License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ContentGrabbingJi</artifactId>
<groupId>net.lamgc</groupId>
<version>3.0.0-alpha-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ContentGrabbingJi-common</artifactId>
<dependencies>
<dependency>
<groupId>net.lamgc</groupId>
<artifactId>ContentGrabbingJi-CacheStore-api</artifactId>
<version>3.0.0-alpha-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,362 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache;
import com.google.common.base.Throwables;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import net.lamgc.cgj.bot.cache.exception.GetCacheStoreException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.function.Function;
/**
* CacheStore 构造器.
*
* <p>这只是个门面类, 最终调用 {@link CacheStoreFactory} 的具体实现类获取 {@link CacheStore} 对象.
* <p> CacheStoreBuilder 确保了返回不为 null,
* 即使所有 Factory 无法返回合适的 CacheStore 实例, 也只会抛出 {@link NoSuchFactoryException} 异常.
* @see CacheStoreFactory
* @author LamGC
*/
public final class CacheStoreBuilder {
private final static Logger log = LoggerFactory.getLogger(CacheStoreBuilder.class);
private volatile List<CacheStoreFactory> factoryList;
private volatile Map<CacheStoreFactory, FactoryInfo> factoryInfoMap;
private final ServiceLoader<CacheStoreFactory> factoryLoader = ServiceLoader.load(CacheStoreFactory.class);
private final File dataDirectory;
private final Object getFactoryLock = new Object();
/**
* 获取 CacheStoreBuilder 实例.
* <p> 该方法仅提供给 ContentGrabbingJiBot 调用, 请勿通过该方法私自创建 CacheStoreBuilder.
* @param cacheDataDirectory 缓存组件数据目录的根目录(例如提供 File 为 {@code /data/cache},
* 缓存组件名为 {@code example}, 则缓存组件的缓存路径为 {@code /data/cache/example}).
* @return 返回新的 CacheStoreBuilder 实例, 各实例之间是没有任何关系的(包括创建的 CacheStore, 除非缓存组件设计错误).
* @throws IOException 当路径检查失败时抛出.
*/
public static CacheStoreBuilder getInstance(File cacheDataDirectory) throws IOException {
Objects.requireNonNull(cacheDataDirectory, "Cache component data directory is null");
if (!cacheDataDirectory.exists() && !cacheDataDirectory.mkdirs()) {
throw new IOException("Data directory creation failed: " + cacheDataDirectory.getAbsolutePath());
} else if (!cacheDataDirectory.isDirectory()) {
throw new IOException("The specified data store path is not a directory: " +
cacheDataDirectory.getAbsolutePath());
} else if (!cacheDataDirectory.canRead() || !cacheDataDirectory.canWrite()) {
throw new IOException("The specified data store directory cannot be read or written.");
}
return new CacheStoreBuilder(cacheDataDirectory);
}
private CacheStoreBuilder(File dataDirectory) {
this.dataDirectory = dataDirectory;
loadFactory();
}
/**
* 使用 SPI 机制加载所有缓存组件.
*
* <p>第一次执行时加载, 由 {@link #getFactory(CacheStoreSource, Function)} 调用.
* <p>由于 ServiceLoader 线程不安全, 所以通过 synchronized 保证其安全性.
* 不通过 块进行初始化的原因是因为担心发生异常导致无法继续执行
* (除非必要, 否则不要使用 执行可能会发生异常的代码.).
*/
private synchronized void loadFactory() {
factoryLoader.reload();
List<CacheStoreFactory> newFactoryList = new ArrayList<>();
Map<CacheStoreFactory, FactoryInfo> newFactoryInfoMap = new HashMap<>(8);
try {
for (CacheStoreFactory factory : factoryLoader) {
FactoryInfo info;
try {
info = new FactoryInfo(factory.getClass());
if (newFactoryInfoMap.containsValue(info)) {
log.warn("发现 Name 重复的 Factory, 已跳过. (被拒绝的实现: {})", factory.getClass().getName());
continue;
}
newFactoryInfoMap.put(factory, info);
} catch (IllegalArgumentException e) {
log.warn("Factory {} 加载失败: {}", factory.getClass().getName(), e.getMessage());
continue;
}
if (!initialFactory(factory, info)) {
log.warn("Factory {} 初始化失败.", info.getFactoryName());
continue;
}
newFactoryList.add(factory);
log.info("Factory {} 已加载(优先级: {}, 实现类: {}).",
info.getFactoryName(),
info.getFactoryPriority(),
factory.getClass().getName());
}
newFactoryList.sort(new PriorityComparator(newFactoryInfoMap));
synchronized (getFactoryLock) {
this.factoryList = newFactoryList;
this.factoryInfoMap = newFactoryInfoMap;
}
} catch (Error error) {
// 防止发生 Error 又不输出到日志导致玄学问题难以排查.
log.error("加载 CacheStoreFactory 时发生严重错误.", error);
throw error;
}
}
/**
* 初始化 Factory.
* @param factory Factory 对象.
* @param info Factory Info.
* @return 初始化成功则返回 {@code true}, 否则返回 {@code false}.
*/
private boolean initialFactory(CacheStoreFactory factory, FactoryInfo info) {
File factoryDataDirectory = new File(dataDirectory, info.getFactoryName());
if (!factoryDataDirectory.exists() && !factoryDataDirectory.mkdirs()) {
log.warn("Factory {} 数据存储目录创建失败, 可能会影响后续操作. (Path: {})",
info.getFactoryName(),
factoryDataDirectory.getAbsolutePath());
}
try {
factory.initial(factoryDataDirectory);
return true;
} catch (Exception e) {
log.error("Factory {} 初始化失败.\n{}", info.getFactoryName(), Throwables.getStackTraceAsString(e));
return false;
}
}
/**
* 优先级排序器.
*/
private static final class PriorityComparator implements Comparator<CacheStoreFactory> {
private final Map<CacheStoreFactory, FactoryInfo> factoryInfoMap;
private PriorityComparator(Map<CacheStoreFactory, FactoryInfo> factoryInfoMap) {
this.factoryInfoMap = factoryInfoMap;
}
@Override
public int compare(CacheStoreFactory o1, CacheStoreFactory o2) {
FactoryInfo info1 = Objects.requireNonNull(factoryInfoMap.get(o1));
FactoryInfo info2 = Objects.requireNonNull(factoryInfoMap.get(o2));
return info2.getFactoryPriority() - info1.getFactoryPriority();
}
}
/**
* 获取一个当前可用的高优先级 Factory 对象.
* @return 返回可用的高优先级 Factory 对象.
*/
private <R extends CacheStore<?>> R getFactory(CacheStoreSource storeSource,
Function<CacheStoreFactory, R> function)
throws NoSuchFactoryException {
Map<CacheStoreFactory, FactoryInfo> localFactoryInfoMap;
Iterator<CacheStoreFactory> iterator;
synchronized (getFactoryLock) {
localFactoryInfoMap = this.factoryInfoMap;
iterator = factoryList.iterator();
}
while (iterator.hasNext()) {
CacheStoreFactory factory = iterator.next();
FactoryInfo info = localFactoryInfoMap.get(factory);
if (storeSource != null && info.getStoreSource() != storeSource) {
continue;
}
try {
if (factory.canGetCacheStore()) {
log.debug("CacheStoreFactory {} 可用(优先级: {}).", info.getFactoryName(), info.getFactoryPriority());
} else {
continue;
}
} catch (Exception e) {
log.error("CacheStoreFactory " + info.getFactoryName() +
" (" + factory.getClass().getName() + ") 检查可用性时发生异常", e);
continue;
}
try {
R result = function.apply(factory);
log.trace("已通过 Factory '{}' 获取 CacheStore '{}'. (Factory实现类: {}).",
info.getFactoryName(),
result.getClass().getName(),
factory.getClass().getName());
return result;
} catch (Exception e) {
if (!iterator.hasNext()) {
throw new NoSuchFactoryException(new GetCacheStoreException("CacheStoreFactory " +
info.getFactoryName() + " (" + factory.getClass().getName() +
") 创建 CacheStore 时发生异常.", e));
} else {
if (e instanceof GetCacheStoreException) {
log.warn("CacheStoreFactory '{} ({})' 无法提供相应 CacheStore. 原因: {}",
info.getFactoryName(), factory.getClass().getName(), e.getMessage());
} else {
log.warn("CacheStoreFactory '" + info.getFactoryName() +
" (" + factory.getClass().getName() + ")' 创建 CacheStore 时发生异常.", e);
}
}
}
}
throw new NoSuchFactoryException();
}
/**
* 检查是否为 {@code null}.
* @param cacheStore 缓存库.
* @param factory 工厂对象.
* @param <V> 缓存库类型.
* @return 如果不为 null, 则正常返回.
* @throws GetCacheStoreException 当 cacheStore 为 {@code null} 时抛出.
*/
private <V extends CacheStore<?>> V returnRequireNonNull(V cacheStore, CacheStoreFactory factory) {
if (cacheStore == null) {
throw new GetCacheStoreException("Factory '" + factory.getClass().getName() + "' returned null");
}
return cacheStore;
}
/**
* 获取单项缓存存储容器.
* @param identify 缓存容器标识.
* @param converter 类型转换器.
* @param <V> 值类型.
* @return 返回新的存储容器, 与其他容器互不干扰.
* @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出.
*/
public <V> SingleCacheStore<V> newSingleCacheStore(String identify, StringConverter<V> converter) {
return newSingleCacheStore(null, identify, converter);
}
/**
* 获取单项缓存存储容器.
* @param storeSource 存储类型.
* @param identify 缓存容器标识.
* @param converter 类型转换器.
* @param <V> 值类型.
* @return 返回新的存储容器, 与其他容器互不干扰.
* @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出.
*/
public <V> SingleCacheStore<V> newSingleCacheStore(CacheStoreSource storeSource, String identify,
StringConverter<V> converter) {
try {
return getFactory(storeSource, factory ->
returnRequireNonNull(factory.newSingleCacheStore(identify, converter), factory));
} catch (NoSuchFactoryException e) {
throw new GetCacheStoreException("无可用的 Factory.", e);
}
}
/**
* 获取列表缓存存储容器.
* @param identify 缓存容器标识.
* @param converter 类型转换器.
* @param <E> 元素类型.
* @return 返回新的存储容器, 与其他容器互不干扰.
* @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出.
*/
public <E> ListCacheStore<E> newListCacheStore(String identify, StringConverter<E> converter) {
return newListCacheStore(null, identify, converter);
}
/**
* 获取列表缓存存储容器.
* @param storeSource 存储类型.
* @param identify 缓存容器标识.
* @param converter 类型转换器.
* @param <E> 元素类型.
* @return 返回新的存储容器, 与其他容器互不干扰.
* @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出.
*/
public <E> ListCacheStore<E> newListCacheStore(CacheStoreSource storeSource, String identify,
StringConverter<E> converter) {
try {
return getFactory(storeSource, factory ->
returnRequireNonNull(factory.newListCacheStore(identify, converter), factory));
} catch (NoSuchFactoryException e) {
throw new GetCacheStoreException("无可用的 Factory.", e);
}
}
/**
* 获取集合缓存存储容器.
* @param identify 缓存容器标识.
* @param converter 类型转换器.
* @param <E> 元素类型.
* @return 返回新的存储容器, 与其他容器互不干扰.
* @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出.
*/
public <E> SetCacheStore<E> newSetCacheStore(String identify, StringConverter<E> converter) {
return newSetCacheStore(null, identify, converter);
}
/**
* 获取集合缓存存储容器.
* @param identify 缓存容器标识.
* @param converter 类型转换器.
* @param <E> 元素类型.
* @return 返回新的存储容器, 与其他容器互不干扰.
* @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出.
*/
public <E> SetCacheStore<E> newSetCacheStore(CacheStoreSource storeSource, String identify,
StringConverter<E> converter) {
try {
return getFactory(storeSource, factory ->
returnRequireNonNull(factory.newSetCacheStore(identify, converter), factory));
} catch (NoSuchFactoryException e) {
throw new GetCacheStoreException("无可用的 Factory.", e);
}
}
/**
* 获取映射表缓存存储容器.
* @param identify 缓存容器标识.
* @param converter 类型转换器.
* @param <V> 值类型.
* @return 返回新的存储容器, 与其他容器互不干扰.
* @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出.
*/
public <V> MapCacheStore<V> newMapCacheStore(String identify, StringConverter<V> converter) {
return newMapCacheStore(null, identify, converter);
}
/**
* 获取映射表缓存存储容器.
* @param storeSource 存储类型.
* @param identify 缓存容器标识.
* @param converter 类型转换器.
* @param <V> 值类型.
* @return 返回新的存储容器, 与其他容器互不干扰.
* @throws GetCacheStoreException 当无法获取可用的 CacheStore 时抛出.
*/
public <V> MapCacheStore<V> newMapCacheStore(CacheStoreSource storeSource, String identify,
StringConverter<V> converter) {
try {
return getFactory(storeSource, factory ->
returnRequireNonNull(factory.newMapCacheStore(identify, converter), factory));
} catch (NoSuchFactoryException e) {
throw new GetCacheStoreException("无可用的 Factory.", e);
}
}
}
@@ -0,0 +1,92 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache;
import com.google.common.base.Strings;
import java.util.Objects;
/**
* CacheStoreFactory 的标识信息.
* @author LamGC
*/
public final class FactoryInfo {
private final String factoryName;
private final int factoryPriority;
private final CacheStoreSource storeSource;
public FactoryInfo(Class<? extends CacheStoreFactory> factoryClass) {
Factory factoryAnnotation = factoryClass.getAnnotation(Factory.class);
if (factoryAnnotation == null) {
throw new IllegalArgumentException("Annotation not found");
} else if (Strings.isNullOrEmpty(factoryAnnotation.name())) {
throw new IllegalArgumentException("Factory name is empty");
}
this.factoryName = factoryAnnotation.name();
this.storeSource = factoryAnnotation.source();
int factoryPriority = factoryAnnotation.priority();
if (factoryPriority > FactoryPriority.PRIORITY_HIGHEST) {
this.factoryPriority = FactoryPriority.PRIORITY_HIGHEST;
} else {
this.factoryPriority = Math.max(FactoryPriority.PRIORITY_LOWEST, factoryPriority);
}
}
/**
* 获取 Factory 声明的名称.
* @return 返回 Factory 名称.
*/
public String getFactoryName() {
return factoryName;
}
/**
* 获取 Factory 优先级.
* @return 返回 Factory 的优先级.
*/
public int getFactoryPriority() {
return factoryPriority;
}
/**
* 获取存储容器实现的存储源类型.
* @return 返回 Factory 所属实现组件的存储源类型.
*/
public CacheStoreSource getStoreSource() {
return storeSource;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
FactoryInfo that = (FactoryInfo) o;
return factoryName.equals(that.factoryName) && storeSource == that.storeSource;
}
@Override
public int hashCode() {
return Objects.hash(factoryName, storeSource);
}
}
@@ -0,0 +1,35 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache;
/**
* 找不到 Factory 异常.
* <p>当尝试获取 {@link CacheStoreFactory} 失败时抛出.
* @see CacheStoreFactory
* @author LamGC
*/
public class NoSuchFactoryException extends Exception {
public NoSuchFactoryException() {
super("Unable to get available factory");
}
public NoSuchFactoryException(Throwable cause) {
super("Unable to get available factory", cause);
}
}
@@ -0,0 +1,130 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.framework;
import java.util.Objects;
/**
* 框架所属平台.
* <p> 用于标识一个平台, 每个平台有唯一的 {@link #platformIdentify PlatformIdentify}.
* <p> {@link #platformName PlatformName} 是允许不同的,
* 只要 {@link #platformIdentify PlatformIdentify} 唯一即可.
*
* <p> {@link #platformIdentify PlatformIdentify}
* 遵循 Camel-Case (骆峰命名法, 每个单词之间无空格, 单词首字母大写),
* 对于平台标识由少量字母组成的则全大写(例如腾讯QQ的平台标识则为 'QQ' 而不是 'qq' 或 'Qq').
*
* <p> 希望各框架组件开发者能够遵循以上规则, 如出现平台识别信息混乱的情况, 不排除会有将 Platform 枚举化(固定 Platform 类型)的情况.
*
* @author LamGC
*/
public final class Platform {
private final String platformName;
private final String platformIdentify;
/**
* 构造一个 Platform 对象.
* @param platformName 平台名
* @param platformIdentify 平台唯一标识名.
*/
public Platform(String platformName, String platformIdentify) {
this.platformName = Objects.requireNonNull(platformName, "PlatformName is null");
this.platformIdentify = Objects.requireNonNull(platformIdentify, "PlatformIdentify is null");
}
@Override
public String toString() {
return "Platform{" +
"platformName='" + platformName + '\'' +
", platformIdentify='" + platformIdentify + '\'' +
'}';
}
/**
* 获取平台名称.
* @return 返回平台名称.
*/
public String getPlatformName() {
return platformName;
}
/**
* 获取平台唯一标识.
* 注意, 该标识将应用于平台所属事件的处理相关.
* @return 返回平台标识.
*/
public String getPlatformIdentify() {
return platformIdentify;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Platform platform = (Platform) o;
return platformIdentify.equals(platform.platformIdentify);
}
@Override
public int hashCode() {
return Objects.hash(platformIdentify);
}
/**
* 标准化平台信息.
* <p> 暂不公开, 仅用于 Platform 实例唯一化.
*/
private enum Standard {
/**
* 腾讯 QQ
*/
Tencent_QQ(new Platform("Tencent QQ", "QQ")),
/**
* 腾讯微信
*/
Tencent_WeChat(new Platform("Tencent WeChat", "WeChat")),
/**
* Telegram(电报)
*/
Telegram(new Platform("Telegram", "Telegram")),
/**.
* Discord
*/
Discord(new Platform("Discord", "Discord")),
/**
* OneBot
*/
OneBot(new Platform("OneBot Http API", "OneBot"))
;
private final Platform platform;
Standard(Platform platform) {
this.platform = platform;
}
public Platform getPlatform() {
return platform;
}
}
}
@@ -0,0 +1,41 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.framework.message;
/**
* 消息来源
* @author LamGC
*/
public enum MessageSource {
/**
* 好友/联系人 私聊消息
*/
PRIVATE,
/**
* 临时私聊事件
*/
TEMP,
/**
* 群组消息
*/
GROUP,
/**
* 讨论组消息
*/
DISCUSS
}
@@ -0,0 +1,358 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import net.lamgc.cgj.bot.cache.convert.StringToStringConverter;
import net.lamgc.cgj.bot.cache.exception.GetCacheStoreException;
import net.lamgc.cgj.bot.cache.factory.*;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* @see CacheStoreBuilder
*/
public class CacheStoreBuilderTest {
private final static TemporaryFolder tempDirectory = TemporaryFolder.builder().build();
private final static Logger log = LoggerFactory.getLogger(CacheStoreBuilderTest.class);
@BeforeClass
public static void beforeAction() throws IOException {
tempDirectory.create();
}
@Test
public void getCacheStoreTest() throws IOException {
final String identify = "test";
final StringConverter<String> converter = new StringToStringConverter();
CacheStoreBuilder cacheStoreBuilder = CacheStoreBuilder.getInstance(tempDirectory.getRoot());
SingleCacheStore<String> singleCacheStore = cacheStoreBuilder.newSingleCacheStore(CacheStoreSource.REMOTE, identify, converter);
Assert.assertNotNull(singleCacheStore);
Assert.assertEquals(RemoteCacheFactory.RemoteSingleCacheFactory.class, singleCacheStore.getClass());
ListCacheStore<String> listCacheStore = cacheStoreBuilder.newListCacheStore(CacheStoreSource.MEMORY, identify, converter);
Assert.assertNotNull(listCacheStore);
Assert.assertEquals(MemoryFactory.MemoryListCacheStore.class, listCacheStore.getClass());
MapCacheStore<String> mapCacheStore = cacheStoreBuilder.newMapCacheStore(CacheStoreSource.LOCAL, identify, converter);
Assert.assertNotNull(mapCacheStore);
Assert.assertEquals(LocalFactory.LocalMapCacheStore.class, mapCacheStore.getClass());
SetCacheStore<String> setCacheStore = cacheStoreBuilder.newSetCacheStore(identify, converter);
Assert.assertNotNull(setCacheStore);
Assert.assertEquals(SetCacheStoreFactory.OnlySetCacheStore.class, setCacheStore.getClass());
}
@Test
public void loadFailureTest() throws IllegalAccessException, NoSuchFieldException, IOException {
CacheStoreBuilder cacheStoreBuilder = CacheStoreBuilder.getInstance(tempDirectory.getRoot());
Field factoryListField;
factoryListField = CacheStoreBuilder.class.getDeclaredField("factoryList");
factoryListField.setAccessible(true);
Object o = factoryListField.get(cacheStoreBuilder);
Assert.assertTrue(o instanceof List);
@SuppressWarnings("unchecked")
List<CacheStoreFactory> factoryList = (List<CacheStoreFactory>) o;
Set<Class<? extends CacheStoreFactory>> classSet = new HashSet<>();
factoryList.forEach(factory -> classSet.add(factory.getClass()));
// 重名检查
if (classSet.contains(DuplicateNameFactoryA.class) && classSet.contains(DuplicateNameFactoryB.class)) {
Assert.fail("There are different factories with the same name");
return;
}
if (classSet.contains(NoAnnotationFactory.class)) {
Assert.fail("Factory without @Factory added is loaded");
return;
}
if (classSet.contains(InitialFailureFactory.class)) {
Assert.fail("The factory that failed to initialize was loaded");
}
}
@Test
public void multiThreadReloadTest() throws IOException, NoSuchMethodException, InterruptedException {
final String identify = "test";
final StringConverter<String> converter = new StringToStringConverter();
final CacheStoreBuilder cacheStoreBuilder = CacheStoreBuilder.getInstance(tempDirectory.getRoot());
final AtomicBoolean uncaughtExceptionFlag = new AtomicBoolean();
final int totalCount = 100000;
final Method loadFactoryMethod = CacheStoreBuilder.class.getDeclaredMethod("loadFactory");
loadFactoryMethod.setAccessible(true);
Thread accessThreadA = new Thread(() -> {
for (int i = 0; i < totalCount; i++) {
SingleCacheStore<String> singleCacheStore = cacheStoreBuilder.newSingleCacheStore(CacheStoreSource.REMOTE, identify, converter);
Assert.assertNotNull(singleCacheStore);
Assert.assertEquals(RemoteCacheFactory.RemoteSingleCacheFactory.class, singleCacheStore.getClass());
ListCacheStore<String> listCacheStore = cacheStoreBuilder.newListCacheStore(CacheStoreSource.MEMORY, identify, converter);
Assert.assertNotNull(listCacheStore);
Assert.assertEquals(MemoryFactory.MemoryListCacheStore.class, listCacheStore.getClass());
MapCacheStore<String> mapCacheStore = cacheStoreBuilder.newMapCacheStore(CacheStoreSource.LOCAL, identify, converter);
Assert.assertNotNull(mapCacheStore);
Assert.assertEquals(LocalFactory.LocalMapCacheStore.class, mapCacheStore.getClass());
SetCacheStore<String> setCacheStore = cacheStoreBuilder.newSetCacheStore(identify, converter);
Assert.assertNotNull(setCacheStore);
Assert.assertEquals(SetCacheStoreFactory.OnlySetCacheStore.class, setCacheStore.getClass());
}
}, "Thread-AccessBuilderA");
Thread reloadThreadA = new Thread(() -> {
int count = 0;
final Random random = new Random();
while(count++ < totalCount) {
if (random.nextInt() % 2 == 0) {
try {
loadFactoryMethod.invoke(cacheStoreBuilder);
} catch (IllegalAccessException | InvocationTargetException e) {
log.error("重载 Builder 时发生异常.",
e instanceof InvocationTargetException ?
((InvocationTargetException) e).getTargetException() :
e);
}
}
}
}, "Thread-ReloadBuilderA");
Thread accessThreadB = new Thread(() -> {
for (int i = 0; i < totalCount; i++) {
SingleCacheStore<String> singleCacheStore = cacheStoreBuilder.newSingleCacheStore(CacheStoreSource.REMOTE, identify, converter);
Assert.assertNotNull(singleCacheStore);
Assert.assertEquals(RemoteCacheFactory.RemoteSingleCacheFactory.class, singleCacheStore.getClass());
ListCacheStore<String> listCacheStore = cacheStoreBuilder.newListCacheStore(CacheStoreSource.MEMORY, identify, converter);
Assert.assertNotNull(listCacheStore);
Assert.assertEquals(MemoryFactory.MemoryListCacheStore.class, listCacheStore.getClass());
MapCacheStore<String> mapCacheStore = cacheStoreBuilder.newMapCacheStore(CacheStoreSource.LOCAL, identify, converter);
Assert.assertNotNull(mapCacheStore);
Assert.assertEquals(LocalFactory.LocalMapCacheStore.class, mapCacheStore.getClass());
SetCacheStore<String> setCacheStore = cacheStoreBuilder.newSetCacheStore(identify, converter);
Assert.assertNotNull(setCacheStore);
Assert.assertEquals(SetCacheStoreFactory.OnlySetCacheStore.class, setCacheStore.getClass());
}
}, "Thread-AccessBuilderB");
Thread reloadThreadB = new Thread(() -> {
int count = 0;
final Random random = new Random();
while(count++ < totalCount) {
if (random.nextInt() % 2 == 0) {
try {
loadFactoryMethod.invoke(cacheStoreBuilder);
} catch (IllegalAccessException | InvocationTargetException e) {
log.error("重载 Builder 时发生异常.",
e instanceof InvocationTargetException ?
((InvocationTargetException) e).getTargetException() :
e);
}
}
}
}, "Thread-ReloadBuilderB");
class TestUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
log.error("An uncapped exception occurred in thread " + t.getName(), e);
uncaughtExceptionFlag.set(true);
}
}
accessThreadA.setUncaughtExceptionHandler(new TestUncaughtExceptionHandler());
reloadThreadA.setUncaughtExceptionHandler(new TestUncaughtExceptionHandler());
accessThreadB.setUncaughtExceptionHandler(new TestUncaughtExceptionHandler());
reloadThreadB.setUncaughtExceptionHandler(new TestUncaughtExceptionHandler());
accessThreadA.start();
reloadThreadA.start();
accessThreadB.start();
reloadThreadB.start();
accessThreadA.join();
reloadThreadA.join();
accessThreadB.join();
reloadThreadB.join();
if (uncaughtExceptionFlag.get()) {
Assert.fail("Exception occurred while multithreading reload");
}
}
@Test
public void noSpecifiedGetCacheStoreTest() throws IOException {
final String identify = "test";
final StringConverter<String> converter = new StringToStringConverter();
CacheStoreBuilder cacheStoreBuilder = CacheStoreBuilder.getInstance(tempDirectory.getRoot());
SingleCacheStore<String> singleCacheStore = cacheStoreBuilder.newSingleCacheStore(identify, converter);
Assert.assertNotNull(singleCacheStore);
ListCacheStore<String> listCacheStore = cacheStoreBuilder.newListCacheStore(identify, converter);
Assert.assertNotNull(listCacheStore);
MapCacheStore<String> mapCacheStore = cacheStoreBuilder.newMapCacheStore(identify, converter);
Assert.assertNotNull(mapCacheStore);
SetCacheStore<String> setCacheStore = cacheStoreBuilder.newSetCacheStore(identify, converter);
Assert.assertNotNull(setCacheStore);
}
@Test
public void noSuchFactoryExceptionThrowTest() throws NoSuchFieldException, IllegalAccessException, IOException {
final String identify = "test";
final StringConverter<String> converter = new StringToStringConverter();
CacheStoreBuilder cacheStoreBuilder = CacheStoreBuilder.getInstance(tempDirectory.getRoot());
Field factoryListField;
factoryListField = CacheStoreBuilder.class.getDeclaredField("factoryList");
factoryListField.setAccessible(true);
Object o = factoryListField.get(cacheStoreBuilder);
Assert.assertTrue(o instanceof List);
@SuppressWarnings("unchecked")
List<CacheStoreFactory> factoryList = (List<CacheStoreFactory>) o;
factoryList.clear();
try {
cacheStoreBuilder.newSingleCacheStore(identify, converter);
} catch (GetCacheStoreException e) {
if (!(e.getCause() instanceof NoSuchFactoryException)) {
Assert.fail("The exception is not due to NoSuchFactoryException");
}
}
try {
cacheStoreBuilder.newMapCacheStore(identify, converter);
} catch (GetCacheStoreException e) {
if (!(e.getCause() instanceof NoSuchFactoryException)) {
Assert.fail("The exception is not due to NoSuchFactoryException");
}
}
try {
cacheStoreBuilder.newListCacheStore(identify, converter);
} catch (GetCacheStoreException e) {
if (!(e.getCause() instanceof NoSuchFactoryException)) {
Assert.fail("The exception is not due to NoSuchFactoryException");
}
}
try {
cacheStoreBuilder.newSetCacheStore(identify, converter);
} catch (GetCacheStoreException e) {
if (!(e.getCause() instanceof NoSuchFactoryException)) {
Assert.fail("The exception is not due to NoSuchFactoryException");
}
}
}
@Test
public void getInstanceFailureTest() throws IOException {
Assert.assertThrows(IOException.class, () ->
CacheStoreBuilder.getInstance(tempDirectory.newFile("invalid file.bin")));
File onlyReadableDirectory = tempDirectory.newFile("onlyReadable");
// Assert.assertTrue(onlyReadableDirectory.setWritable(false));
Assert.assertThrows(IOException.class, () ->
CacheStoreBuilder.getInstance(new File(onlyReadableDirectory, "cache")));
Assert.assertNotNull(
CacheStoreBuilder.getInstance(new File(tempDirectory.newFolder("valid directory"), "cache")));
}
@Test
public void lastFactoryThrowExceptionTest() throws NoSuchFieldException, IllegalAccessException, IOException {
final String identify = "test";
final StringConverter<String> converter = new StringToStringConverter();
CacheStoreBuilder cacheStoreBuilder = CacheStoreBuilder.getInstance(tempDirectory.getRoot());
Field factoryListField;
factoryListField = CacheStoreBuilder.class.getDeclaredField("factoryList");
factoryListField.setAccessible(true);
Object o = factoryListField.get(cacheStoreBuilder);
Assert.assertTrue(o instanceof List);
@SuppressWarnings("unchecked")
List<CacheStoreFactory> factoryList = (List<CacheStoreFactory>) o;
factoryList.removeIf(factory -> !(factory instanceof GetCacheStoreExceptionFactory));
try {
cacheStoreBuilder.newSingleCacheStore(identify, converter);
} catch (GetCacheStoreException e) {
if (!(e.getCause() instanceof NoSuchFactoryException)) {
Assert.fail("The exception is not due to NoSuchFactoryException");
}
}
try {
cacheStoreBuilder.newMapCacheStore(identify, converter);
} catch (GetCacheStoreException e) {
if (!(e.getCause() instanceof NoSuchFactoryException)) {
Assert.fail("The exception is not due to NoSuchFactoryException");
}
}
try {
cacheStoreBuilder.newListCacheStore(identify, converter);
} catch (GetCacheStoreException e) {
if (!(e.getCause() instanceof NoSuchFactoryException)) {
Assert.fail("The exception is not due to NoSuchFactoryException");
}
}
try {
cacheStoreBuilder.newSetCacheStore(identify, converter);
} catch (GetCacheStoreException e) {
if (!(e.getCause() instanceof NoSuchFactoryException)) {
Assert.fail("The exception is not due to NoSuchFactoryException");
}
}
}
}
@@ -0,0 +1,88 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache;
import net.lamgc.cgj.bot.cache.factory.*;
import org.junit.Assert;
import org.junit.Test;
/**
* @see FactoryInfo
*/
public class FactoryInfoTest {
@Test
public void analyticTest() {
FactoryInfo infoA = new FactoryInfo(FactoryInfoTestFactory.class);
Assert.assertEquals("test-factoryInfo", infoA.getFactoryName());
Assert.assertEquals(6, infoA.getFactoryPriority());
Assert.assertEquals(CacheStoreSource.MEMORY, infoA.getStoreSource());
FactoryInfo infoB = new FactoryInfo(FactoryInfoTestFactory.class);
Assert.assertEquals(infoA, infoB);
Assert.assertEquals(infoA.hashCode(), infoB.hashCode());
}
@Test
@SuppressWarnings({"SimplifiableAssertion", "EqualsWithItself", "ConstantConditions"})
public void equalsTest() {
FactoryInfo infoA = new FactoryInfo(FactoryInfoTestFactory.class);
FactoryInfo infoB = new FactoryInfo(FactoryInfoTestFactory.class);
Assert.assertTrue(infoA.equals(infoA));
Assert.assertTrue(infoA.equals(infoB));
Assert.assertFalse(infoA.equals(null));
Assert.assertFalse(infoA.equals(new Object()));
Assert.assertFalse(
new FactoryInfo(NameNoEqualFactoryA.class)
.equals(new FactoryInfo(NameNoEqualFactoryB.class)));
Assert.assertFalse(
new FactoryInfo(NameNoEqualFactoryA.class)
.equals(new FactoryInfo(CacheStoreSourceNoEqualFactory.class)));
}
@Test(expected = IllegalArgumentException.class)
public void noAnnotationTest() {
new FactoryInfo(NoAnnotationFactory.class);
}
@Test(expected = IllegalArgumentException.class)
public void unnamedTest() {
new FactoryInfo(UnnamedFactory.class);
}
@Test
public void priorityCheckTest() {
FactoryInfo toHighPriorityFactoryInfo = new FactoryInfo(TooHighPriorityFactory.class);
Assert.assertEquals(FactoryPriority.PRIORITY_HIGHEST,
toHighPriorityFactoryInfo.getFactoryPriority());
FactoryInfo toLowPriorityFactoryInfo = new FactoryInfo(TooLowPriorityFactory.class);
Assert.assertEquals(FactoryPriority.PRIORITY_LOWEST,
toLowPriorityFactoryInfo.getFactoryPriority());
Assert.assertNotEquals(toHighPriorityFactoryInfo, toLowPriorityFactoryInfo);
}
}
@@ -0,0 +1,57 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.factory;
import net.lamgc.cgj.bot.cache.*;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import net.lamgc.cgj.bot.cache.exception.GetCacheStoreException;
import java.io.File;
@Factory(name = "test-throwException")
public class AvailabilityCheckExceptionThrowFactory implements CacheStoreFactory {
@Override
public void initial(File dataDirectory) {
}
@Override
public <V> SingleCacheStore<V> newSingleCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException {
return null;
}
@Override
public <E> ListCacheStore<E> newListCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException {
return null;
}
@Override
public <E> SetCacheStore<E> newSetCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException {
return null;
}
@Override
public <V> MapCacheStore<V> newMapCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException {
return null;
}
@Override
public boolean canGetCacheStore() {
throw new IllegalStateException();
}
}
@@ -0,0 +1,57 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.factory;
import net.lamgc.cgj.bot.cache.*;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import net.lamgc.cgj.bot.cache.exception.GetCacheStoreException;
import java.io.File;
@Factory(name = "test-NameNoEqualA", source = CacheStoreSource.REMOTE)
public class CacheStoreSourceNoEqualFactory implements CacheStoreFactory {
@Override
public void initial(File dataDirectory) {
}
@Override
public <V> SingleCacheStore<V> newSingleCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException {
return null;
}
@Override
public <E> ListCacheStore<E> newListCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException {
return null;
}
@Override
public <E> SetCacheStore<E> newSetCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException {
return null;
}
@Override
public <V> MapCacheStore<V> newMapCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException {
return null;
}
@Override
public boolean canGetCacheStore() {
return false;
}
}
@@ -0,0 +1,57 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.factory;
import net.lamgc.cgj.bot.cache.*;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import net.lamgc.cgj.bot.cache.exception.GetCacheStoreException;
import java.io.File;
@Factory(name = "duplicate")
public class DuplicateNameFactoryA implements CacheStoreFactory {
@Override
public void initial(File dataDirectory) {
}
@Override
public <V> SingleCacheStore<V> newSingleCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException {
return null;
}
@Override
public <E> ListCacheStore<E> newListCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException {
return null;
}
@Override
public <E> SetCacheStore<E> newSetCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException {
return null;
}
@Override
public <V> MapCacheStore<V> newMapCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException {
return null;
}
@Override
public boolean canGetCacheStore() {
return false;
}
}
@@ -0,0 +1,57 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.factory;
import net.lamgc.cgj.bot.cache.*;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import net.lamgc.cgj.bot.cache.exception.GetCacheStoreException;
import java.io.File;
@Factory(name = "duplicate", priority = FactoryPriority.PRIORITY_HIGHEST)
public class DuplicateNameFactoryB implements CacheStoreFactory {
@Override
public void initial(File dataDirectory) {
}
@Override
public <V> SingleCacheStore<V> newSingleCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException {
return null;
}
@Override
public <E> ListCacheStore<E> newListCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException {
return null;
}
@Override
public <E> SetCacheStore<E> newSetCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException {
return null;
}
@Override
public <V> MapCacheStore<V> newMapCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException {
return null;
}
@Override
public boolean canGetCacheStore() {
return false;
}
}
@@ -0,0 +1,57 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.factory;
import net.lamgc.cgj.bot.cache.*;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import net.lamgc.cgj.bot.cache.exception.GetCacheStoreException;
import java.io.File;
@Factory(name = "test-factoryInfo", source = CacheStoreSource.MEMORY, priority = 6)
public class FactoryInfoTestFactory implements CacheStoreFactory {
@Override
public void initial(File dataDirectory) {
}
@Override
public <V> SingleCacheStore<V> newSingleCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException {
return null;
}
@Override
public <E> ListCacheStore<E> newListCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException {
return null;
}
@Override
public <E> SetCacheStore<E> newSetCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException {
return null;
}
@Override
public <V> MapCacheStore<V> newMapCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException {
return null;
}
@Override
public boolean canGetCacheStore() {
return false;
}
}
@@ -0,0 +1,57 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.factory;
import net.lamgc.cgj.bot.cache.*;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import net.lamgc.cgj.bot.cache.exception.GetCacheStoreException;
import java.io.File;
@Factory(name = "test-getCacheStoreException", priority = FactoryPriority.PRIORITY_HIGHEST)
public class GetCacheStoreExceptionFactory implements CacheStoreFactory {
@Override
public void initial(File dataDirectory) {
}
@Override
public <V> SingleCacheStore<V> newSingleCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException {
throw new IllegalStateException();
}
@Override
public <E> ListCacheStore<E> newListCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException {
throw new IllegalStateException();
}
@Override
public <E> SetCacheStore<E> newSetCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException {
throw new IllegalStateException();
}
@Override
public <V> MapCacheStore<V> newMapCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException {
throw new IllegalStateException();
}
@Override
public boolean canGetCacheStore() {
return true;
}
}
@@ -0,0 +1,57 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.factory;
import net.lamgc.cgj.bot.cache.*;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import net.lamgc.cgj.bot.cache.exception.GetCacheStoreException;
import java.io.File;
@Factory(name = "initialFailure")
public class InitialFailureFactory implements CacheStoreFactory {
@Override
public void initial(File dataDirectory) {
throw new IllegalStateException();
}
@Override
public <V> SingleCacheStore<V> newSingleCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException {
return null;
}
@Override
public <E> ListCacheStore<E> newListCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException {
return null;
}
@Override
public <E> SetCacheStore<E> newSetCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException {
return null;
}
@Override
public <V> MapCacheStore<V> newMapCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException {
return null;
}
@Override
public boolean canGetCacheStore() {
return false;
}
}
@@ -0,0 +1,153 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.factory;
import net.lamgc.cgj.bot.cache.*;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import net.lamgc.cgj.bot.cache.exception.GetCacheStoreException;
import org.junit.Assert;
import java.io.File;
import java.util.Map;
import java.util.Set;
@Factory(name = "test-local", source = CacheStoreSource.LOCAL)
public class LocalFactory implements CacheStoreFactory {
@Override
public void initial(File dataDirectory) {
Assert.assertNotNull(dataDirectory);
}
@Override
public <V> SingleCacheStore<V> newSingleCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException {
return null;
}
@Override
public <E> ListCacheStore<E> newListCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException {
return null;
}
@Override
public <E> SetCacheStore<E> newSetCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException {
return null;
}
@Override
public <V> MapCacheStore<V> newMapCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException {
return new LocalMapCacheStore<>();
}
@Override
public boolean canGetCacheStore() {
return true;
}
public static class LocalMapCacheStore<V> implements MapCacheStore<V> {
@Override
public int mapSize(CacheKey key) {
return 0;
}
@Override
public Set<String> mapFieldSet(CacheKey key) {
return null;
}
@Override
public Set<V> mapValueSet(CacheKey key) {
return null;
}
@Override
public boolean put(CacheKey key, String field, V value) {
return false;
}
@Override
public boolean putAll(CacheKey key, Map<? extends String, ? extends V> map) {
return false;
}
@Override
public boolean putIfNotExist(CacheKey key, String field, V value) {
return false;
}
@Override
public V get(CacheKey key, String field) {
return null;
}
@Override
public boolean removeField(CacheKey key, String field) {
return false;
}
@Override
public boolean containsField(CacheKey key, String field) {
return false;
}
@Override
public boolean mapIsEmpty(CacheKey key) {
return false;
}
@Override
public boolean clearMap(CacheKey key) {
return false;
}
@Override
public boolean setTimeToLive(CacheKey key, long ttl) {
return false;
}
@Override
public long getTimeToLive(CacheKey key) {
return 0;
}
@Override
public long size() {
return 0;
}
@Override
public boolean clear() {
return false;
}
@Override
public boolean exists(CacheKey key) {
return false;
}
@Override
public boolean remove(CacheKey key) {
return false;
}
@Override
public Set<String> keySet() {
return null;
}
}
}
@@ -0,0 +1,149 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.factory;
import net.lamgc.cgj.bot.cache.*;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import net.lamgc.cgj.bot.cache.exception.GetCacheStoreException;
import org.junit.Assert;
import java.io.File;
import java.util.Collection;
import java.util.List;
import java.util.Set;
@Factory(name = "test-memory", source = CacheStoreSource.MEMORY)
public class MemoryFactory implements CacheStoreFactory {
@Override
public void initial(File dataDirectory) {
Assert.assertNotNull(dataDirectory);
}
@Override
public <V> SingleCacheStore<V> newSingleCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException {
return null;
}
@Override
public <E> ListCacheStore<E> newListCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException {
return new MemoryListCacheStore<>();
}
@Override
public <E> SetCacheStore<E> newSetCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException {
return null;
}
@Override
public <V> MapCacheStore<V> newMapCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException {
return null;
}
@Override
public boolean canGetCacheStore() {
return true;
}
public static class MemoryListCacheStore<E> implements ListCacheStore<E> {
@Override
public E getElement(CacheKey key, int index) {
return null;
}
@Override
public List<E> getElementsByRange(CacheKey key, int index, int length) {
return null;
}
@Override
public boolean removeElement(CacheKey key, int index) {
return false;
}
@Override
public boolean addElement(CacheKey key, E element) {
return false;
}
@Override
public boolean addElements(CacheKey key, Collection<E> elements) {
return false;
}
@Override
public boolean containsElement(CacheKey key, E element) {
return false;
}
@Override
public boolean isEmpty(CacheKey key) {
return false;
}
@Override
public int elementsLength(CacheKey key) {
return 0;
}
@Override
public boolean clearCollection(CacheKey key) {
return false;
}
@Override
public boolean removeElement(CacheKey key, E element) {
return false;
}
@Override
public boolean setTimeToLive(CacheKey key, long ttl) {
return false;
}
@Override
public long getTimeToLive(CacheKey key) {
return 0;
}
@Override
public long size() {
return 0;
}
@Override
public boolean clear() {
return false;
}
@Override
public boolean exists(CacheKey key) {
return false;
}
@Override
public boolean remove(CacheKey key) {
return false;
}
@Override
public Set<String> keySet() {
return null;
}
}
}
@@ -0,0 +1,57 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.factory;
import net.lamgc.cgj.bot.cache.*;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import net.lamgc.cgj.bot.cache.exception.GetCacheStoreException;
import java.io.File;
@Factory(name = "test-NameNoEqualA")
public class NameNoEqualFactoryA implements CacheStoreFactory {
@Override
public void initial(File dataDirectory) {
}
@Override
public <V> SingleCacheStore<V> newSingleCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException {
return null;
}
@Override
public <E> ListCacheStore<E> newListCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException {
return null;
}
@Override
public <E> SetCacheStore<E> newSetCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException {
return null;
}
@Override
public <V> MapCacheStore<V> newMapCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException {
return null;
}
@Override
public boolean canGetCacheStore() {
return false;
}
}
@@ -0,0 +1,57 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.factory;
import net.lamgc.cgj.bot.cache.*;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import net.lamgc.cgj.bot.cache.exception.GetCacheStoreException;
import java.io.File;
@Factory(name = "test-NameNoEqualB")
public class NameNoEqualFactoryB implements CacheStoreFactory {
@Override
public void initial(File dataDirectory) {
}
@Override
public <V> SingleCacheStore<V> newSingleCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException {
return null;
}
@Override
public <E> ListCacheStore<E> newListCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException {
return null;
}
@Override
public <E> SetCacheStore<E> newSetCacheStore(String identify, StringConverter<E> converter) throws GetCacheStoreException {
return null;
}
@Override
public <V> MapCacheStore<V> newMapCacheStore(String identify, StringConverter<V> converter) throws GetCacheStoreException {
return null;
}
@Override
public boolean canGetCacheStore() {
return false;
}
}

Some files were not shown because too many files have changed in this diff Show More