mirror of
https://github.com/LamGC/ContentGrabbingJi.git
synced 2025-04-30 06:37:36 +00:00
Compare commits
No commits in common. "v2.5.2-20200610.4-SNAPSHOT" and "3.0.0" have entirely different histories.
v2.5.2-202
...
3.0.0
103
.github/CONTRIBUTING.md
vendored
Normal file
103
.github/CONTRIBUTING.md
vendored
Normal 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 按照以上规则填写即可,但对于依赖项而言,则要按照 GAV(GroupId:ArtifactId:Version)坐标去除 **Version** 项后填写,
|
||||
例如:`[Add] junit:junit 添加 Junit 单元测试依赖项;`,
|
||||
或者更新依赖项需要添加对依赖项版本号的变更:`[Update] junit:junit 更新 Junit 以修复潜在的漏洞('4.13' -> '4.13.1');`
|
||||
|
||||
#### 引用 Github 中的相关 Issue 或者 PR ####
|
||||
如需引用相关 Issue 或者 PR(Pull 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
.github/ISSUE_TEMPLATE/Bug_Report.md
vendored
26
.github/ISSUE_TEMPLATE/Bug_Report.md
vendored
@ -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
.github/workflows/maven-test.yml
vendored
Normal file
39
.github/workflows/maven-test.yml
vendored
Normal 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/redis-github-action@1.1.0
|
||||
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
|
44
.gitignore
vendored
44
.gitignore
vendored
@ -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
CODE_OF_CONDUCT.md
Normal file
76
CODE_OF_CONDUCT.md
Normal 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 [lamgc@outlook.com](mailto:lamgc@outlook.com). 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
|
16
ContentGrabbingJi-CacheStore-api/README.md
Normal file
16
ContentGrabbingJi-CacheStore-api/README.md
Normal file
@ -0,0 +1,16 @@
|
||||
# CacheStore-API #
|
||||
|
||||
如需开发更多缓存组件,至少需要实现以下接口:
|
||||
- CacheStore / CollectionCacheStore
|
||||
- `CacheStore` 是所有缓存容器的父接口。
|
||||
- 这两个类为抽象接口,定义了部分具体接口的公共方法。
|
||||
- 不一定要**单独**实现该接口,可以**直接实现**具体的接口(比如 `SingleCacheStore`)
|
||||
- CacheStoreFactory
|
||||
- 你还需要为其添加 `@Factory` 注解,否则不会生效。
|
||||
- SingleCacheStore / MapCacheStore / SetCacheStore / ListCacheStore
|
||||
- `MapCacheStore`、`SetCacheStore` 和 `ListCacheStore` 是 `CollectionCacheStore` 的子类。
|
||||
- 至少需要提供其中一种实现才能算是一个有效的缓存组件。尚未实现的部分将会由其他组件代替。
|
||||
|
||||
完成缓存组件的开发后,应按照 SPI 机制的要求,设置所属 CacheStoreFactory 为 Service。
|
||||
|
||||
正常情况下,该模块不需要进行更改,即使需要更改,也需要保证向后兼容性。
|
32
ContentGrabbingJi-CacheStore-api/pom.xml
Normal file
32
ContentGrabbingJi-CacheStore-api/pom.xml
Normal 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>
|
114
ContentGrabbingJi-CacheStore-api/src/main/java/net/lamgc/cgj/bot/cache/CacheKey.java
vendored
Normal file
114
ContentGrabbingJi-CacheStore-api/src/main/java/net/lamgc/cgj/bot/cache/CacheKey.java
vendored
Normal file
@ -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);
|
||||
}
|
||||
}
|
92
ContentGrabbingJi-CacheStore-api/src/main/java/net/lamgc/cgj/bot/cache/CacheStore.java
vendored
Normal file
92
ContentGrabbingJi-CacheStore-api/src/main/java/net/lamgc/cgj/bot/cache/CacheStore.java
vendored
Normal file
@ -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();
|
||||
|
||||
}
|
94
ContentGrabbingJi-CacheStore-api/src/main/java/net/lamgc/cgj/bot/cache/CacheStoreFactory.java
vendored
Normal file
94
ContentGrabbingJi-CacheStore-api/src/main/java/net/lamgc/cgj/bot/cache/CacheStoreFactory.java
vendored
Normal file
@ -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();
|
||||
|
||||
}
|
37
ContentGrabbingJi-CacheStore-api/src/main/java/net/lamgc/cgj/bot/cache/CacheStoreSource.java
vendored
Normal file
37
ContentGrabbingJi-CacheStore-api/src/main/java/net/lamgc/cgj/bot/cache/CacheStoreSource.java
vendored
Normal file
@ -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
|
||||
}
|
94
ContentGrabbingJi-CacheStore-api/src/main/java/net/lamgc/cgj/bot/cache/CollectionCacheStore.java
vendored
Normal file
94
ContentGrabbingJi-CacheStore-api/src/main/java/net/lamgc/cgj/bot/cache/CollectionCacheStore.java
vendored
Normal file
@ -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);
|
||||
|
||||
}
|
57
ContentGrabbingJi-CacheStore-api/src/main/java/net/lamgc/cgj/bot/cache/Factory.java
vendored
Normal file
57
ContentGrabbingJi-CacheStore-api/src/main/java/net/lamgc/cgj/bot/cache/Factory.java
vendored
Normal file
@ -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;
|
||||
|
||||
}
|
61
ContentGrabbingJi-CacheStore-api/src/main/java/net/lamgc/cgj/bot/cache/FactoryPriority.java
vendored
Normal file
61
ContentGrabbingJi-CacheStore-api/src/main/java/net/lamgc/cgj/bot/cache/FactoryPriority.java
vendored
Normal file
@ -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() {}
|
||||
|
||||
}
|
79
ContentGrabbingJi-CacheStore-api/src/main/java/net/lamgc/cgj/bot/cache/ListCacheStore.java
vendored
Normal file
79
ContentGrabbingJi-CacheStore-api/src/main/java/net/lamgc/cgj/bot/cache/ListCacheStore.java
vendored
Normal file
@ -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);
|
||||
}
|
129
ContentGrabbingJi-CacheStore-api/src/main/java/net/lamgc/cgj/bot/cache/MapCacheStore.java
vendored
Normal file
129
ContentGrabbingJi-CacheStore-api/src/main/java/net/lamgc/cgj/bot/cache/MapCacheStore.java
vendored
Normal file
@ -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);
|
||||
|
||||
}
|
37
ContentGrabbingJi-CacheStore-api/src/main/java/net/lamgc/cgj/bot/cache/SetCacheStore.java
vendored
Normal file
37
ContentGrabbingJi-CacheStore-api/src/main/java/net/lamgc/cgj/bot/cache/SetCacheStore.java
vendored
Normal file
@ -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 的那套进行设计, 但是我希望不要出现偏向性, 为所有实现提供一个平衡的实现复杂度)
|
||||
*/
|
||||
}
|
57
ContentGrabbingJi-CacheStore-api/src/main/java/net/lamgc/cgj/bot/cache/SingleCacheStore.java
vendored
Normal file
57
ContentGrabbingJi-CacheStore-api/src/main/java/net/lamgc/cgj/bot/cache/SingleCacheStore.java
vendored
Normal file
@ -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);
|
||||
|
||||
}
|
42
ContentGrabbingJi-CacheStore-api/src/main/java/net/lamgc/cgj/bot/cache/convert/Converter.java
vendored
Normal file
42
ContentGrabbingJi-CacheStore-api/src/main/java/net/lamgc/cgj/bot/cache/convert/Converter.java
vendored
Normal file
@ -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);
|
||||
}
|
||||
}
|
63
ContentGrabbingJi-CacheStore-api/src/test/java/net/lamgc/cgj/bot/cache/CacheKeyTest.java
vendored
Normal file
63
ContentGrabbingJi-CacheStore-api/src/test/java/net/lamgc/cgj/bot/cache/CacheKeyTest.java
vendored
Normal file
@ -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());
|
||||
}
|
||||
|
||||
}
|
40
ContentGrabbingJi-CacheStore-local/pom.xml
Normal file
40
ContentGrabbingJi-CacheStore-local/pom.xml
Normal file
@ -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>
|
122
ContentGrabbingJi-CacheStore-local/src/main/java/net/lamgc/cgj/bot/cache/local/AutoCleanTimer.java
vendored
Normal file
122
ContentGrabbingJi-CacheStore-local/src/main/java/net/lamgc/cgj/bot/cache/local/AutoCleanTimer.java
vendored
Normal file
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
33
ContentGrabbingJi-CacheStore-local/src/main/java/net/lamgc/cgj/bot/cache/local/Cleanable.java
vendored
Normal file
33
ContentGrabbingJi-CacheStore-local/src/main/java/net/lamgc/cgj/bot/cache/local/Cleanable.java
vendored
Normal file
@ -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();
|
||||
}
|
||||
}
|
163
ContentGrabbingJi-CacheStore-local/src/main/java/net/lamgc/cgj/bot/cache/local/HashCacheStore.java
vendored
Normal file
163
ContentGrabbingJi-CacheStore-local/src/main/java/net/lamgc/cgj/bot/cache/local/HashCacheStore.java
vendored
Normal file
@ -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());
|
||||
}
|
||||
}
|
63
ContentGrabbingJi-CacheStore-redis/pom.xml
Normal file
63
ContentGrabbingJi-CacheStore-redis/pom.xml
Normal file
@ -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>
|
50
ContentGrabbingJi-CacheStore-redis/src/main/java/net/lamgc/cgj/bot/cache/redis/LuaScript.java
vendored
Normal file
50
ContentGrabbingJi-CacheStore-redis/src/main/java/net/lamgc/cgj/bot/cache/redis/LuaScript.java
vendored
Normal file
@ -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;
|
||||
}
|
||||
}
|
110
ContentGrabbingJi-CacheStore-redis/src/main/java/net/lamgc/cgj/bot/cache/redis/RedisCacheStore.java
vendored
Normal file
110
ContentGrabbingJi-CacheStore-redis/src/main/java/net/lamgc/cgj/bot/cache/redis/RedisCacheStore.java
vendored
Normal file
@ -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;
|
||||
}
|
||||
}
|
77
ContentGrabbingJi-CacheStore-redis/src/main/java/net/lamgc/cgj/bot/cache/redis/RedisUtils.java
vendored
Normal file
77
ContentGrabbingJi-CacheStore-redis/src/main/java/net/lamgc/cgj/bot/cache/redis/RedisUtils.java
vendored
Normal file
@ -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
ContentGrabbingJi-Event-api/Readme.md
Normal file
40
ContentGrabbingJi-Event-api/Readme.md
Normal 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
ContentGrabbingJi-Event-api/pom.xml
Normal file
32
ContentGrabbingJi-Event-api/pom.xml
Normal 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
ContentGrabbingJi-Function-api/pom.xml
Normal file
32
ContentGrabbingJi-Function-api/pom.xml
Normal 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
ContentGrabbingJi-Function-pixiv/pom.xml
Normal file
45
ContentGrabbingJi-Function-pixiv/pom.xml
Normal 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>
|
50
ContentGrabbingJi-Template-Velocity/pom.xml
Normal file
50
ContentGrabbingJi-Template-Velocity/pom.xml
Normal file
@ -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
ContentGrabbingJi-Template-api/pom.xml
Normal file
46
ContentGrabbingJi-Template-api/pom.xml
Normal 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
ContentGrabbingJi-common/pom.xml
Normal file
39
ContentGrabbingJi-common/pom.xml
Normal 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>
|
362
ContentGrabbingJi-common/src/main/java/net/lamgc/cgj/bot/cache/CacheStoreBuilder.java
vendored
Normal file
362
ContentGrabbingJi-common/src/main/java/net/lamgc/cgj/bot/cache/CacheStoreBuilder.java
vendored
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
92
ContentGrabbingJi-common/src/main/java/net/lamgc/cgj/bot/cache/FactoryInfo.java
vendored
Normal file
92
ContentGrabbingJi-common/src/main/java/net/lamgc/cgj/bot/cache/FactoryInfo.java
vendored
Normal file
@ -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);
|
||||
}
|
||||
}
|
35
ContentGrabbingJi-common/src/main/java/net/lamgc/cgj/bot/cache/NoSuchFactoryException.java
vendored
Normal file
35
ContentGrabbingJi-common/src/main/java/net/lamgc/cgj/bot/cache/NoSuchFactoryException.java
vendored
Normal file
@ -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
|
||||
}
|
358
ContentGrabbingJi-common/src/test/java/net/lamgc/cgj/bot/cache/CacheStoreBuilderTest.java
vendored
Normal file
358
ContentGrabbingJi-common/src/test/java/net/lamgc/cgj/bot/cache/CacheStoreBuilderTest.java
vendored
Normal file
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
88
ContentGrabbingJi-common/src/test/java/net/lamgc/cgj/bot/cache/FactoryInfoTest.java
vendored
Normal file
88
ContentGrabbingJi-common/src/test/java/net/lamgc/cgj/bot/cache/FactoryInfoTest.java
vendored
Normal file
@ -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;
|
||||
}
|
||||
}
|
153
ContentGrabbingJi-common/src/test/java/net/lamgc/cgj/bot/cache/factory/LocalFactory.java
vendored
Normal file
153
ContentGrabbingJi-common/src/test/java/net/lamgc/cgj/bot/cache/factory/LocalFactory.java
vendored
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
149
ContentGrabbingJi-common/src/test/java/net/lamgc/cgj/bot/cache/factory/MemoryFactory.java
vendored
Normal file
149
ContentGrabbingJi-common/src/test/java/net/lamgc/cgj/bot/cache/factory/MemoryFactory.java
vendored
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
57
ContentGrabbingJi-common/src/test/java/net/lamgc/cgj/bot/cache/factory/NameNoEqualFactoryA.java
vendored
Normal file
57
ContentGrabbingJi-common/src/test/java/net/lamgc/cgj/bot/cache/factory/NameNoEqualFactoryA.java
vendored
Normal file
@ -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;
|
||||
}
|
||||
}
|
57
ContentGrabbingJi-common/src/test/java/net/lamgc/cgj/bot/cache/factory/NameNoEqualFactoryB.java
vendored
Normal file
57
ContentGrabbingJi-common/src/test/java/net/lamgc/cgj/bot/cache/factory/NameNoEqualFactoryB.java
vendored
Normal file
@ -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
Loading…
Reference in New Issue
Block a user