Compare commits

..

No commits in common. "3.0.0" and "v2.5.1" have entirely different histories.

264 changed files with 7566 additions and 15210 deletions

View File

@ -1,103 +0,0 @@
# 贡献指南 #
> 警告:该指南尚未完成,所以在遇到与贡献有关问题时应该及时联系项目管理者以确定有关贡献事项准则,
如该指南未提及有关事项时,你不应该擅作主张地决定事项准则,擅作主张将会在 Review 阶段造成不必要的麻烦(甚至会被拒绝)。
!!!这条消息将在贡献指南完善的差不多后去除。!!!
欢迎你为本项目作出贡献!本项目的发展离不开所有贡献者(不仅仅只是提交了相关代码,帮助编写文档,还是提出问题或想法也算是贡献者之一!)的贡献。
在为本项目作出任何贡献之前,请先阅读[贡献者准则](https://github.com/LamGC/ContentGrabbingJi/blob/3.0.0/CODE_OF_CONDUCT.md)
一旦你尝试(或正在)为本项目贡献(贡献中,或者贡献过),即代表你**认同并接受**贡献者准则,**清楚明白且接受**因**违反贡献者准则**所造成的后果。
## 代码规范 ##
本项目代码规范大部分遵循 《Alibaba Java Coding Guidelines》
详细内容可前往 https://github.com/alibaba/p3c 查阅。
如果你的 IDE 为 Eclipse 或 Idea则推荐你为 IDE 添加阿里巴巴根据该规范开发的规范检查插件,该插件有助于你保持代码规范。
## Commit Message ##
提交信息需要遵循以下格式:
```
[TAG] ModuleName 对提交的说明;
[TAG] FileName/ClassName 对该文件的说明;
...
```
#### 标点符号的使用 ####
在提交信息中,符号使用**英文符号**。
#### 文件名或类名的说明 ####
对于类代码文件而言FileName 只需要填写类名,对其他文件则需要包括后缀名。
FileName 和 ModuleName 可按需要使用`, `追加,注意与说明空一格,
例如: `[Add][Change] SimpleClass, MainClass 添加 SimpleClass 并调整 MainClass 的日志格式;`
ModuleName 根据修改的相关子模块名填写即可,对项目而非子模块的更改,则 ModuleName 为 `Project`,一个例子:
```
[Change][Update][Optimize] Project, SubModule01, WebFrontend 更新项目中的Readme, 修改 SubModule01 中鉴权的实现细节, 优化Web前端模块的启动速度;
[Update] Readme.md 调整文档格式;
[Change] AuthorizationChecker 调整鉴权过程以避免潜在的错误;
[Optimize] MainClass 优化初始化速度, 移除不必要的检查步骤;
```
#### 标签的使用 ####
目前支持以下Tag:
- `Add` 文件添加
- `Change` 文件更改
- `Update` 更新文件
- `Delete` 文件删除
- `Document` 文档相关
- `Fix` 问题修复
- `Optimize` 优化相关(或者代码整理啥的)
- `Move` 文件移动(例如包更改)
- `Rename` 更改名称(类名, 文件名等)
- `Issue:Id` 指定 Commit 所关联的 Github Issue如果与其他标签有关可直接加到指定标签后方该标签可不用加
- `PR:ID` 指定 Commit 所关联的 Pull Request可选的可直接使用 Github 的默认 Commit Message
> 注意:后续如有需要,会追加一些标签。
对大部分文件而言FileName 按照以上规则填写即可,但对于依赖项而言,则要按照 GAVGroupId:ArtifactId:Version坐标去除 **Version** 项后填写,
例如:`[Add] junit:junit 添加 Junit 单元测试依赖项;`
或者更新依赖项需要添加对依赖项版本号的变更:`[Update] junit:junit 更新 Junit 以修复潜在的漏洞('4.13' -> '4.13.1');`
#### 引用 Github 中的相关 Issue 或者 PR ####
如需引用相关 Issue 或者 PRPull Request可在相关 Tag 中引用,或者另起一个 Tag 并声明指定的 ID例如
```
[Fix #11] Module 修复了部分功能异常情况;
...
```
或者是:
```
[Change][Issue #15] Module 增加了一些功能;
...
```
对于需要修复的相关 Issue既可以采用第一种形式也可以用第二种形式单独添加一个 Issue 标签标明关联的 Issue 即可,比如这样:
```
[Fix][Issue #17] Module 修复了返回异常的问题;
...
```
如确定修复完成,也可以直接使用 Github 会对其作出相关操作的词(?),比如:
```
[Fix close#20] Module 修复响应码判断错误的问题;
...
```
或者是
```
[Fix][Issue fixed#28] Module 修复模板编译错误的问题;
...
```
## 代码质量 ##
如果你想添加新的功能,请务必对新功能相关代码编写好覆盖全,情况完善的单元测试(尽可能的覆盖所有新增功能,确保对大部分情况都有足够的测试)!
覆盖全、多角度、高度完善的单元测试是保证代码、项目质量必不可少的!
每次提交后,请检查 Action查看 Github 中的代码是否能通过所有的单元测试。
## 依赖管理 ##
> 请**尽可能避免引入**新依赖项,如果某个依赖项的引入,仅仅是用于某些计算(或某个小功能,比如仅使用 Apache Commons 中的 `RandomUtils` 类获取 long 类型的随机数),
则这个依赖项的引入是完全没有必要的。
对于因极少需要而引入某些依赖项时,请考虑了解依赖项内的*实现细节*,并复现在项目中,以避免引入依赖项。
(当然,这是有例外的,如果某个功能较为复杂,且在该依赖项拥有很好的质量保证时,可考虑引入,具体细节可发起**issue**与我们进行讨论。)
**另外!**引入依赖项请注意**License**,确保依赖项可以被本项目使用。

View File

@ -1,39 +0,0 @@
# 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

38
.gitignore vendored
View File

@ -1,36 +1,6 @@
# Compiled class file
*.class
# Log file
/pluginData/
/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
# maven build directory
target/
# Test run directory
/test/
*/test/
/CGJ_2.iml
/cookies.store
/target/

View File

@ -1,76 +0,0 @@
# 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

View File

@ -1,16 +0,0 @@
# CacheStore-API #
如需开发更多缓存组件,至少需要实现以下接口:
- CacheStore / CollectionCacheStore
- `CacheStore` 是所有缓存容器的父接口。
- 这两个类为抽象接口,定义了部分具体接口的公共方法。
- 不一定要**单独**实现该接口,可以**直接实现**具体的接口(比如 `SingleCacheStore`
- CacheStoreFactory
- 你还需要为其添加 `@Factory` 注解,否则不会生效。
- SingleCacheStore / MapCacheStore / SetCacheStore / ListCacheStore
- `MapCacheStore`、`SetCacheStore` 和 `ListCacheStore``CollectionCacheStore` 的子类。
- 至少需要提供其中一种实现才能算是一个有效的缓存组件。尚未实现的部分将会由其他组件代替。
完成缓存组件的开发后,应按照 SPI 机制的要求,设置所属 CacheStoreFactory 为 Service。
正常情况下,该模块不需要进行更改,即使需要更改,也需要保证向后兼容性。

View File

@ -1,32 +0,0 @@
<?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>

View File

@ -1,114 +0,0 @@
/*
* 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);
}
}

View File

@ -1,92 +0,0 @@
/*
* 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();
}

View File

@ -1,94 +0,0 @@
/*
* 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();
}

View File

@ -1,37 +0,0 @@
/*
* 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
}

View File

@ -1,94 +0,0 @@
/*
* 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);
}

View File

@ -1,57 +0,0 @@
/*
* 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;
}

View File

@ -1,61 +0,0 @@
/*
* 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() {}
}

View File

@ -1,79 +0,0 @@
/*
* 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);
}

View File

@ -1,129 +0,0 @@
/*
* 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);
}

View File

@ -1,37 +0,0 @@
/*
* 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 的那套进行设计, 但是我希望不要出现偏向性, 为所有实现提供一个平衡的实现复杂度)
*/
}

View File

@ -1,57 +0,0 @@
/*
* 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);
}

View File

@ -1,42 +0,0 @@
/*
* 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);
}

View File

@ -1,27 +0,0 @@
/*
* 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> {
}

View File

@ -1,35 +0,0 @@
/*
* 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;
}
}

View File

@ -1,34 +0,0 @@
/*
* 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);
}
}

View File

@ -1,63 +0,0 @@
/*
* 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));
}
}

View File

@ -1,38 +0,0 @@
/*
* 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));
}
}

View File

@ -1,39 +0,0 @@
/*
* 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());
}
}

View File

@ -1,40 +0,0 @@
<?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>

View File

@ -1,122 +0,0 @@
/*
* 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();
}
}
}

View File

@ -1,33 +0,0 @@
/*
* 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;
}

View File

@ -1,95 +0,0 @@
/*
* 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();
}
}

View File

@ -1,163 +0,0 @@
/*
* 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));
}
}
}

View File

@ -1,119 +0,0 @@
/*
* 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());
}
}

View File

@ -1,47 +0,0 @@
/*
* 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();
}
}

View File

@ -1,54 +0,0 @@
/*
* 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();
}
}

View File

@ -1,70 +0,0 @@
/*
* 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;
}
}

View File

@ -1,111 +0,0 @@
/*
* 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);
}
}

View File

@ -1,35 +0,0 @@
#
# 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

View File

@ -1,91 +0,0 @@
/*
* 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);
}
}

View File

@ -1,139 +0,0 @@
/*
* 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)));
}
}

View File

@ -1,132 +0,0 @@
/*
* 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"));
}
}

View File

@ -1,45 +0,0 @@
/*
* 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));
}
}

View File

@ -1,176 +0,0 @@
/*
* 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));
}
}

View File

@ -1,93 +0,0 @@
/*
* 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());
}
}

View File

@ -1,63 +0,0 @@
<?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>

View File

@ -1,50 +0,0 @@
/*
* 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;
}
}

View File

@ -1,110 +0,0 @@
/*
* 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;
}
}

View File

@ -1,86 +0,0 @@
/*
* 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();
}
}

View File

@ -1,232 +0,0 @@
/*
* 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();
}
}
}

View File

@ -1,128 +0,0 @@
/*
* 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;
}
}

View File

@ -1,158 +0,0 @@
/*
* 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;
}
}

View File

@ -1,171 +0,0 @@
/*
* 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);
}
}

View File

@ -1,82 +0,0 @@
/*
* 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;
}
}

View File

@ -1,77 +0,0 @@
/*
* 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);
}
}

View File

@ -1,25 +0,0 @@
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

View File

@ -1,31 +0,0 @@
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

View File

@ -1,18 +0,0 @@
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

View File

@ -1,35 +0,0 @@
#
# 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

View File

@ -1,211 +0,0 @@
/*
* 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));
}
}
}

View File

@ -1,379 +0,0 @@
/*
* 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));
}
}
}

View File

@ -1,414 +0,0 @@
/*
* 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());
}
}

View File

@ -1,142 +0,0 @@
/*
* 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));
}
}

View File

@ -1,45 +0,0 @@
/*
* 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));
}
}

View File

@ -1,34 +0,0 @@
/*
* 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;
}
}

View File

@ -1,95 +0,0 @@
/*
* 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();
}
}

View File

@ -1,40 +0,0 @@
# 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 模块中,目前暂不需要开发其他实现。

View File

@ -1,32 +0,0 @@
<?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>

View File

@ -1,36 +0,0 @@
/*
* 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;
}
}

View File

@ -1,66 +0,0 @@
/*
* 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;
}
}

View File

@ -1,38 +0,0 @@
/*
* 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();
}

View File

@ -1,45 +0,0 @@
/*
* 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;
}

View File

@ -1,35 +0,0 @@
/*
* 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();
}

View File

@ -1,62 +0,0 @@
/*
* 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);
}
}

View File

@ -1,44 +0,0 @@
/*
* 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);
}

View File

@ -1,57 +0,0 @@
/*
* 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;
}

View File

@ -1,93 +0,0 @@
/*
* 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));
}
}
}

View File

@ -1,62 +0,0 @@
/*
* 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;
}
}

View File

@ -1,30 +0,0 @@
/*
* 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) {
}
}

View File

@ -1,33 +0,0 @@
/*
* 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;
}
}

View File

@ -1,32 +0,0 @@
<?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>

View File

@ -1,45 +0,0 @@
<?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>

View File

@ -1,50 +0,0 @@
<?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>

View File

@ -1,46 +0,0 @@
<?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>

View File

@ -1,39 +0,0 @@
<?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>

View File

@ -1,362 +0,0 @@
/*
* 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);
}
}
}

View File

@ -1,92 +0,0 @@
/*
* 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);
}
}

View File

@ -1,35 +0,0 @@
/*
* 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);
}
}

View File

@ -1,130 +0,0 @@
/*
* 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;
}
}
}

View File

@ -1,41 +0,0 @@
/*
* 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
}

View File

@ -1,358 +0,0 @@
/*
* 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");
}
}
}
}

View File

@ -1,88 +0,0 @@
/*
* 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);
}
}

View File

@ -1,57 +0,0 @@
/*
* 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();
}
}

View File

@ -1,57 +0,0 @@
/*
* 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;
}
}

View File

@ -1,57 +0,0 @@
/*
* 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;
}
}

View File

@ -1,57 +0,0 @@
/*
* 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;
}
}

View File

@ -1,57 +0,0 @@
/*
* 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;
}
}

View File

@ -1,57 +0,0 @@
/*
* 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;
}
}

View File

@ -1,57 +0,0 @@
/*
* 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;
}
}

View File

@ -1,153 +0,0 @@
/*
* 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;
}
}
}

View File

@ -1,149 +0,0 @@
/*
* 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;
}
}
}

View File

@ -1,57 +0,0 @@
/*
* 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;
}
}

View File

@ -1,57 +0,0 @@
/*
* 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;
}
}

View File

@ -1,56 +0,0 @@
/*
* 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;
public class NoAnnotationFactory 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 true;
}
}

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