0

47 Commits

Author SHA1 Message Date
b2ef9b25e3 gradle:LibsPlugin: обновление фильтрации зависимостей 2021-01-08 20:13:51 +03:00
d602296032 gradle:LibsPlugin: изменение пакета 2021-01-08 20:11:21 +03:00
9461a70f92 gradle:LibsPlugin: объединение отдельных классов в один файл 2021-01-08 20:10:02 +03:00
d621e9c4f4 gradle: безопасное использование properties 2021-01-08 20:07:34 +03:00
0eed089809 Merge branch 'feature/phantom-classes' into dev/modules 2021-01-08 20:05:57 +03:00
f2ab5521d1 избавляемся от рефлексии: CommnadManager: register 2021-01-08 20:04:58 +03:00
d686f56b0c избавляемся от рефлексии: скины для голов 2021-01-08 18:53:47 +03:00
070485e951 Merge branch 'develop' into dev/modules
# Conflicts:
#	build.gradle
2021-01-08 18:18:06 +03:00
16ae1743c7 update README.MD 2021-01-08 17:05:34 +03:00
8afba72b77 Merge branch 'feature/textured-skull' into develop 2021-01-08 16:54:32 +03:00
002dae285c добавлен метод установки текстурированной головы игрока (skull) 2021-01-08 16:54:07 +03:00
c0f02d20d5 добавлен метод копирования Location 2021-01-08 16:06:48 +03:00
d0f1ea7b90 gradle: вынесена общая логика gradle-modules 2021-01-07 09:24:54 +03:00
d74265db60 gradle: авто-версионирование с параметром 2021-01-07 08:23:11 +03:00
97bf4b93f1 gradle: добавлена автоматизация версионирования 2021-01-07 08:09:27 +03:00
368c495886 gradle: разделение на gradle-module's 2021-01-07 04:47:46 +03:00
9a14817ff8 gradle: перенесены репозитарии в плагин 2021-01-07 03:41:38 +03:00
132dd2f4ca gradle: убран лишний плагин 2021-01-07 02:25:29 +03:00
1a23102f2e gradle: libs.gradle преобразован в gradle-plugin 2021-01-07 01:42:10 +03:00
f09f0e7a89 gradle: gradle-plugin вынесен в buildSrc 2021-01-07 01:23:00 +03:00
05f0245323 gradle: перенесены методы в gradle-plugin 2021-01-07 00:57:53 +03:00
c98c5d2c34 gradle: перенесены зависимости в отдельный файл 2021-01-06 23:29:32 +03:00
c47bb2093e удалена ненужная зависимость 2021-01-05 19:03:21 +03:00
64c4f20d97 Merge branch 'dev/docs' into develop 2021-01-05 18:59:24 +03:00
89bca95d9f update README.MD 2021-01-05 18:22:31 +03:00
897045b8d2 fix README.MD 2021-01-05 18:20:09 +03:00
0a648b7a5d fix README.MD 2021-01-05 18:18:29 +03:00
b1615e258e update README.MD 2021-01-05 18:17:27 +03:00
1e529d9d55 update README.MD 2021-01-05 17:40:43 +03:00
9929b0af18 fix README.MD 2021-01-05 17:29:36 +03:00
d63b9a085c update README.MD 2021-01-05 17:28:51 +03:00
8e20d31227 update README.MD 2021-01-05 17:13:15 +03:00
1c1075e2dc add README.MD 2021-01-04 02:17:44 +03:00
9d65461f1d Merge branch 'feature/jdbctemplate' into develop 2021-01-03 16:15:44 +03:00
3037aed8ef убран лишний код 2021-01-03 16:09:24 +03:00
f821691308 JdbcTemplate: add queryOne method 2021-01-03 16:07:40 +03:00
c963c4bd60 JdbcTemplate: refactoring 2021-01-03 16:00:34 +03:00
5315a4ad94 Merge branch 'feature/scheduler' into develop 2021-01-03 00:23:17 +03:00
bd91b19114 ScheduleManager: отложенное выполнение задач 2021-01-03 00:22:55 +03:00
e36d0fd75d Merge branch 'dev/refactory' into develop 2021-01-03 00:02:15 +03:00
f9f6fbc541 Merge branch 'feature/jdbctemplate' into develop 2021-01-03 00:01:39 +03:00
395b0a015f add tests for JdbcTemplate 2021-01-03 00:00:29 +03:00
c0c1ede462 add JdbcTemplate
like Spring Data
2021-01-02 20:27:11 +03:00
5f2e208453 Merge branch 'feature/scheduler' into develop 2021-01-02 14:39:05 +03:00
b3aec1716a add ScheduleTask 2021-01-02 14:38:41 +03:00
c20df9af24 ScheduleManager: move to another package 2021-01-02 14:26:54 +03:00
0165a7d424 move AssetsManager to another package 2021-01-02 03:01:28 +03:00
44 changed files with 2442 additions and 111 deletions

679
README.MD Normal file
View File

@@ -0,0 +1,679 @@
# GHAST TOOLS
![version: 1.11-SNAPSHOT](https://img.shields.io/badge/version-1.11--SNAPSHOT-0a0.svg?style=flat)
![bukkit-api: 1.12](https://img.shields.io/badge/bukkit--api-1.12-d50.svg?style=flat)
Набор вспомогательных инструментов для Bukkit API.
_Основан на версии Bukkit API 1.12._
## Содержание
1. [Перед использованием](#перед-использованием)
2. [GhastTools](#ghasttools)
3. [AssetsManager](#assetsmanager)
4. [BuildHelper](#buildhelper)
5. [CommandManager](#commandmanager)
6. [EffectsHelper](#effectshelper)
7. [EventContext](#eventcontext)
8. [JdbcTemplate](#jdbctemplate)
9. [ScheduleManager](#schedulemanager)
10. [ScheduleTask](#scheduletask)
11. [I18n](#i18n)
12. [XLog](#xlog)
13. [Подключение](#подключение)
1. [Gradle](#gradle)
2. [Maven](#maven)
---
## Перед использованием
Перед началом использования, необходимо в вашем Bukkit-плагине прописать подобный код:
```java
void onLoad() {
GhastTools.setPlugin(this);
}
```
Это необходимо сделать, т.к. весь инструментарий основан на статических (static) методах.
Все методы данного набора инструментов объеденены в классы, выполняющие роль группировщиков.
## GhastTools
Методы общего назначения или без определённой группировки.
### loadConfig
Загрузка файла настроек плагина - `config.yml`.
```java
YamlConfiguration config = GhastTools.loadConfig();
```
По-умолчанию "гаст" пытается файл найти в папке плагина - `getDataFolder()`.
Если файла там нет, то выгружает встроенный (имеющийся в `.jar` файле плагина) в эту папку и загружает его.
```java
YamlConfiguration config = GhastTools.loadConfig(false);
```
Если передать параметр `false`, то при отсутствии файла `config.yml` в папке плагина,
будет загруден исключительно встроенный файл настроек.
### copyLocation
Копирования объекта `Location`
```java
Location location = ...;
Location copyLoc = GhastTools.copyLocation(location);
```
## AssetsManager
Методы по работе с файлами плагина (_"ассетами"_).
У каждой группы методов один и тот же набор входных параметров:
- `resourceName` - наименование и путь к файлу в папке плагина
- `defaultResourceName` - наименование и путь к файлу в плагине.
_Опционально. По-умолчанию равнен `resourceName`_
- `saveDefault` - необходимость скопировать содержимое файла из `defaultResourceName` в файл `resourceName`.
_Опционально. По-умолчанию равен `true`_
Правила поиска файлов так же одинаков для каждой группы:
- В начале файл ищется в папке плагина
- Если файл отсутствует в папке плагина, то...
- если `defaultResourceName` не равен `null`...
- если `saveDefault` равен `true`, то файл из плагина будет выгружен в папку плагина и от туда загружен в память.
- если `saveDefault` равен `false`, то данные будут взяты из файлв в плагине.
- если `defaultResourceName` равен `null`, то будет брошено исключение `AssetsException` с описанием ошибки вида "Файл X не найден".
### getAsInputStream
```java
InputStream inputStream = AssetsManager.getAsInputStream("translate.ru.yml", "translate.yml", false);
```
### getAsReader
```java
Reader reader = AssetsManager.getAsReader("translate.ru.yml", "translate.yml", false);
```
### getAsString
```java
String string = AssetsManager.getAsString("readme.txt", "readme.txt", StandardCharsets.UTF_8, false);
```
У этой группы есть дополнительный _опциональный_ параметр - `charset` - в котором указывается кодиривка получаемой строки.
о-умолчанию равен `StandardCharsets.UTF_8`_
## BuildHelper
Набор методов облегчающих размецение объектов на карте.
### placeSkull
Установка черепа.
```java
Location location = ...;
Skull skull = BuildHelper.placeSkull(location, BlockFace.NORTH);
skull.update(true); // иначе изменения на карте не применятся и череп будет висеть в воздухе
```
### placePlayerHead
Установка головы игрока.
```java
Location location = ...;
Skull playerHead = BuildHelper.placePlayerHead(location, BlockFace.NORTH);
playerHead.update(true); // иначе изменения на карте не применятся и голова будет висеть в воздухе
```
Если третьим параметром передать URL текстуры, то голова будет текстурирована.
```java
Location location = ...;
BuildHelper.placePlayerHead(location, BlockFace.NORTH, "http://...");
```
### setPlayerHeadSkin
Установка текстуры для головы игрока.
```java
Location location = ...;
Skull playerHead = BuildHelper.placePlayerHead(location, BlockFace.NORTH);
playerHead.update(true); // иначе изменения на карте не применятся и голова будет висеть в воздухе
setPlayerHeadSkin(playerHead);
```
Порядок выполнения методов в приведённом выше примере **важен**. Если `playerHead.update(true)` вызвать после
установки текстуры, она собъётся на стандартную.
### placeSignWall
```java
Location location = ...;
Sign signWall = BuildHelper.placeSignWall(location, BlockFace.NORTH)
```
## CommandManager
Регистрация команд.
Имеется два варианта использования: упрощённый
```java
CommandManager.register("start", (sender, args) -> sender.sendMessage("hello!"));
```
и подробный:
```java
CommandManager.create("start")
.useOnlyPlayer()
.executer((sender, args) -> sender.sendMessage("hello!"))
.register();
```
### register
Упрощенная регистрация команды. Указывается лишь название команды и исполнитель.
```java
CommandManager.register("start", (sender, args) -> sender.sendMessage("hello!"));
```
### create
Конструктор для подробного варианта регистрации команды.
```java
CommandManager.Builder builder = CommandManager.create("start");
```
### executer
Указание исполнителя для команды
```java
CommandManager.Builder builder = CommandManager.create("start")
.executer((sender, args) -> sender.sendMessage("hello!"));
```
### onError
Обработчик исключений
```java
CommandManager.Builder builder = CommandManager.create("start")
.onError((sender, commandName, args, exception) -> {
sender.sendMessage(ChatColor.RED + "Произошла ошибка при выполнении команды '" + commandName + "'.");
exception.printStackTrace();
});
```
### useOnlyPlayer
Указание, что данную команду могут использовать только Игроки.
Опционально можно указать сообщение, которое будет выводиться в консоль.
_Отменяет действие указателя `useOnlyConsole`_
```java
CommandManager.Builder builder = CommandManager.create("start")
.useOnlyPlayer("Команду могут использовать только игроки");
```
### useOnlyConsole
Указание, что данную команду можно использовать только в консоле.
Опционально можно указать сообщение, которое будет выводиться Игроку.
_Отменяет действие указателя `useOnlyPlayer`_
```java
CommandManager.Builder builder = CommandManager.create("start")
.useOnlyConsole(ChatColor.RED + "Команду можно использовать только в консоли");
```
### register
Регистрация описанной в Конструкторе команды.
```java
CommandManager.create("start")
.useOnlyPlayer()
.executer((sender, args) -> sender.sendMessage("hello!"))
.register();
```
## EffectsHelper
Набор методов для работы с эффектами.
### playSound
Воспроизвести звук.
```java
Location location = ...;
EffectsHelper.playSound(location, Sound.AMBIENT_CAVE, 1.0f);
```
### particle
Создание частиц.
```java
Location location = ...;
EffectsHelper.particle(location, Particle.REDSTONE, 1.0d, 1.0d, 1.0d, 1.0d, 5);
```
## EventContext
Регистрация группы обработчиков событий, объединённых общим условием выполнения.
```java
EventContext.create()
.filter(() -> Bukkit.getOnlinePlayers().size() > 10)
.onEvent(PlayerJoinEvent.class, event -> { event.getPlayer().kickPlayer("Max players"); });
```
### create
Создание контекста событий.
```java
EventContext eventContext = EventContext.create();
```
### filter
Условие, при котором будут срабатывать обработчики событий в данном контексте.
```java
EventContext.create()
.filter(() -> Bukkit.getOnlinePlayers().size() > 10)
```
### onEvent
Указание события и его обработчика.
_Обработчик события регистрируется сразу же._
```java
EventContext.create()
.filter(() -> Bukkit.getOnlinePlayers().size() > 10)
.onEvent(PlayerJoinEvent.class, event -> event.getPlayer().kickPlayer("Max players"));
```
### cancelEvent
Отменить событие.
_Обработчик события регистрируется сразу же._
```java
EventContext.create()
.cancelEvent(BlockPlaceEvent.class);
```
эквивалентен коду:
```java
EventContext.create()
.onEvent(BlockPlaceEvent.class, event -> event.setCancelled(true))
```
## JdbcTemplate
Инструмент для упрощения работы с SQL базами данных, работающими через JDBC.
Для начала потребуется создать объект `DataSource`
```java
// На примере MySQL
MysqlDataSource dataSource = new MysqlDataSource();
dataSource.setServerName("localhost");
dataSource.setPort(3306);
dataSource.setCharacterEncoding(StandardCharsets.UTF_8.name());
dataSource.setDatabaseName("MyDataBase");
dataSource.setUser("root");
dataSource.setPassword("secret");
```
После чего создать `JdbcTemplate`
```java
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
```
### execute
Выполнение SQL запроса без последующей обработки результатов выполнения.
```java
jdbcTemplate.execute("CREATE TABLE my_table (id int, name varchar(16));");
```
### query
Выполнение SQL запроса и обработка его результатов.
Может возвращать любой тип объектов.
```java
String name = jdbcTemplate.query("SELECT name FROM my_table LIMIT 0,1", resultSet -> {
if (resultSet.next()) {
return resultSet.getString("name");
} else {
return null;
}
});
```
```java
List<String> names = jdbcTemplate.query("SELECT name FROM my_table", resultSet -> {
if (resultSet.next()) {
List<String> list = new ArrayList<>();
do {
list.add(resultSet.getString("name"));
} while (resultSet.next());
return list;
} else {
return Collections.emptyList();
}
});
```
### queryOne
Выполнение SQL запроса с расчетом, что результат будет единичным либо не будет вовсе.
Возвращает `Optional`.
```java
Optional<String> optName = jdbcTemplate.queryOne("SELECT name FROM my_table WHERE name LIKE 'dmitriymx'", rs -> rs.getString("name"));
```
### queryList
Выполнение SQL запроса и обработка результата как списка данных.
Возвращает `List`.
```java
List<String> names = jdbcTemplate.queryList("SELECT name FROM my_table", (resultSet, rowNum) -> resultSet.getString("name"));
```
### queryForMap
Выполнение SQL запроса с расчетом, что результат будет единичным либо не будет вовсе.
Возвращает `Map<String, Object>`, где ключ — это наименование колонок таблицы, а значения — это значения в ячейках таблицы.
```java
Map<String, Object> map = jdbcTemplate.queryForMap("SELECT * FROM my_table LIMIT 0,1;");
```
### queryForMapList
Выполнение SQL запроса и обработка результата как списка данных.
Возвращает `List<Map<String, Object>>`, где ключ — это наименование колонок таблицы, а значения — это значения в ячейках таблицы.
```java
List<Map<String, Object>> mapList = jdbcTemplate.queryForMapList("SELECT * FROM my_table");
```
### update
Выполнение SQL запроса где будет происходить обновление данных в таблице.
Под "обновлением" подразумеваются любые изменения в таблице: `UPDATE`, `DELETE`, `INSERT`.
Возвращает число строк, которые были _по факту обновлены_ в таблице.
```java
int rows = jdbcTemplate.update("DELETE FROM my_table WHERE name LIKE 'dmitriymx';");
```
## ScheduleManager
Набор методов для создания параллельных задач, выполняющихся один раз или по рассписанию.
### createTask
Создание конструктора задачи.
```java
ScheduleManager.Builder builder = ScheduleManager.createTask();
```
### useBukkitScheduler
Если задача будет взаимодействоватьс **Bukkit API** или необходима привязка задачи к _тикам_,
то необходимо использовать данный указатель.
В ином случае, указатель не нужен.
```java
ScheduleManager.Builder builder = ScheduleManager.createTask()
.useBukkitScheduler();
```
### after
Указание, что задачу нужно выполнить не сразу, а с некоторой задержкой перед запуском.
```java
ScheduleManager.Builder builder = ScheduleManager.createTask()
.after(5, TimeUnit.MINUTES);
```
### every
Указание, что задачу нужно повторять через указанное время.
```java
ScheduleManager.Builder builder = ScheduleManager.createTask()
.every(5, TimeUnit.MINUTES);
```
### create
Создание описанной задачи.
```java
ScheduleTask scheduleTask = ScheduleManager.createTask()
.every(1, TimeUnit.SECONDS)
.create(() -> Bukkit.getServer().getLogger().info("TimeMS: " + System.currentTimeMillis()));
```
_Задача будет только создана. Для её выполнения нужно вызвать `scheduleTask.start()`._
### execute
Создание и выполнение описанной задачи.
```java
ScheduleTask scheduleTask = ScheduleManager.createTask()
.every(1, TimeUnit.SECONDS)
.execute(() -> Bukkit.getServer().getLogger().info("TimeMS: " + System.currentTimeMillis()));
```
## ScheduleTask
Вспомогательный объект, созданный через `ScheduleManager`. Позволяет управлять созданной задачей.
### start
Запускает задачу, если она еще не запущена.
```java
ScheduleTask scheduleTask = ...;
scheduleTask.start();
```
### isCanceled
Возвращает состояние задачи. Если `true`, значит задача была или _отменена/остоновлена_ или была завершена.
```java
ScheduleTask scheduleTask = ...;
boolean status = scheduleTask.isCanceled();
```
### cancel
Отменяет/Остонавливает выполнение задачи.
```java
ScheduleTask scheduleTask = ...;
scheduleTask.cancel();
```
## I18n
Инструмент для работы с мультиязыковыми сообщениями или просто сообщениями, которые храняться в отдельном файле.
Позволяет использовать шаблонизированные сообщения вида `Привет, {player}!`.
### loadMessages
Загрузка сообщений в инструмент.
Передать можно как "мапу" с перечислением ключ-сообщение, так и `Reader` на файл в формате `key=message` (как у `Properties`).
```java
Map<String, String> messagesMap = ...;
I18n.loadMessages(messagesMap);
```
```java
Reader reader = AssetsManager.getAsReader("messages.properties");
I18n.loadMessages(reader);
```
В первом параметре можно указать код языка, для которого загружаются сообщения. По-умолчанию будет "en".
```java
Map<String, String> enMessagesMap = ...;
Map<String, String> ruMessagesMap = ...;
I18n.loadMessages("en", enMessagesMap);
I18n.loadMessages("ru", ruMessagesMap);
```
```java
Reader readerEn = AssetsManager.getAsReader("messages.properties");
Reader readerRu = AssetsManager.getAsReader("messages.ru.properties");
I18n.loadMessages("en", readerEn);
I18n.loadMessages("ru", readerRu);
```
### get
Получение сообщения по его ключу.
```java
String msg = I18n.get("player.join.msg");
```
Если следующим сообщением указать `Map<String, Object>`, то можно будет воспользоваться шаблонизатором.
```java
Map<String, String> messagesMap = new HashMap<>();
messagesMap.put("player.join.msg", "Привет, {player}!");
I18n.loadMessages(messagesMap);
Map<String, String> params = new HashMap<>();
params.put("player", event.getPlayer().getName());
String msg = I18n.get("player.join.msg", params);
```
Однако можно создавать `Map<String, String> params` явно, а воспользоваться [paramBuilder()](#parambuilder)
```java
String msg = I18n.get("player.join.msg", I18n.paramBuilder()
.add("player", event.getPlayer().getName())
.build());
```
Можно первым параметром указать код языка.
```java
String msg = I18n.get("ru", "player.join.msg");
```
### paramBuilder
Инструмент для параметизирования шаблонов сообщений.
```java
Map<String, Object> params = I18n.paramBuilder()
.add("player", event.getPlayer().getName())
.build();
String msg = I18n.get("player.join.msg", params);
```
## XLog
Замена стандартному `getLogger()`, который использует `java.utils.Logger` и не всегда удобен для логирования.
Имеет 4 уровня логирования: `debug`, `info`, `warning`, `error`.
Сообщения могут быть шаблонизированными. Синтаксис шаблонов — `java.text.MessageFormat`.
Примеры:
```java
XLog.info("Hello");
XLog.info("Player {0} join game", event.getPlayer().getName());
XLog.error("ERROR!", exception);
XLog.error("ERROR: {0}", exception.getMessage());
// Для экранирования "{" нужно перед ней поставить "'".
// А для использования "'" нужно их дублировать.
XLog.error("ERROR ''{0}'' in Event '{{1}'}: {2}", exception.getClass(), event.getClass(), exception.getMessage());
XLog.error("ERROR: {0}", exception.getMessage(), exception);
```
---
## Подключение
### Gradle
```groovy
repositories {
maven { url 'https://dmx-mc-project.gitlab.io/maven-repository/' }
}
```
```groovy
implementation group: 'ghast', name: 'ghast-tools', version: '1.11-SNAPSHOT'
```
### Maven
```xml
<repositories>
<repository>
<id>dmx-mc-project</id>
<url>https://dmx-mc-project.gitlab.io/maven-repository/</url>
</repository>
</repositories>
```
```xml
<dependencies>
<dependency>
<groupId>ghast</groupId>
<artifactId>ghast-tools</artifactId>
<version>1.11-SNAPSHOT</version>
</dependency>
</dependencies>
```

View File

@@ -1,50 +1,35 @@
plugins { subprojects {
id 'java' apply plugin: 'java'
} apply plugin: LibsPlugin
apply plugin: LogicPlugin
def publishScript = file(rootProject.getProjectDir().getPath() + '/publish.gradle') def publishScript = rootDir.toPath().resolve('publish.gradle').toFile()
if (publishScript.exists()) { if (publishScript.exists()) {
apply from: publishScript.path apply from: publishScript
} }
project.group = projectGroup project.group = projectGroup
project.version = projectVersion if (project.hasProperty('moduleVersion')) {
project.version = project.property('moduleVersion')
}
if (project.hasProperty('moduleName')) {
jar.archiveBaseName.set(project.property('moduleName') as String)
}
repositories { repositories {
mavenLocal() mavenLocal()
mavenCentral() mavenCentral()
maven { url 'https://hub.spigotmc.org/nexus/content/groups/public' }
maven { url 'https://dmx-mc-project.gitlab.io/maven-repository/' }
}
ext {
libs = [
bukkit: [lib: 'org.bukkit:bukkit:1.12.2-R0.1-SNAPSHOT', exclude: [
'com.google.code.gson:gson',
'com.googlecode.json-simple:json-simple',
'commons-lang:commons-lang',
'org.yaml:snakeyaml'
]],
commons_text: 'org.apache.commons:commons-text:1.9',
lombok: 'org.projectlombok:lombok:1.18.12',
reflection_object: 'ru.dmitriymx:reflection-object:1.0-BETA'
]
}
def compileOnly2(library) {
dependencies.compileOnly library.lib, {
library.exclude.each { String excludeLibStr ->
String[] excludeLib = excludeLibStr.split(':')
exclude group: excludeLib[0], module: excludeLib[1]
}
}
} }
dependencies { dependencies {
compileOnly libs.lombok compileOnly libs.lombok
annotationProcessor libs.lombok annotationProcessor libs.lombok
compileOnly2 libs.bukkit testImplementation libs.test.junit5
implementation libs.commons_text testImplementation libs.test.mock
implementation libs.reflection_object }
test {
useJUnitPlatform()
}
} }

View File

@@ -0,0 +1,71 @@
import org.gradle.api.Action
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.artifacts.repositories.MavenArtifactRepository
class LibsPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
project.extensions.create('libs', LibsExtention)
project.extensions.create('repos', ReposExtention)
}
}
class ReposExtention {
final def mavenSpigotMC = createSimpleMavenRepo('SpigotMC repository', 'https://hub.spigotmc.org/nexus/content/groups/public')
final def mavenDmxMcProject = createSimpleMavenRepo('DmitriyMX MC Project repository', 'https://dmx-mc-project.gitlab.io/maven-repository/')
private static Action<MavenArtifactRepository> createSimpleMavenRepo(String name, String url) {
return new Action<MavenArtifactRepository>() {
@Override
void execute(MavenArtifactRepository mavenArtifactRepository) {
mavenArtifactRepository.setName(name)
mavenArtifactRepository.setUrl(url)
}
}
}
}
class LibsExtention {
private def junit_version = '5.5.2'
final def commons_text = 'org.apache.commons:commons-text:1.9'
final def lombok = 'org.projectlombok:lombok:1.18.12'
final def bukkit = filter([
lib : 'org.bukkit:bukkit:1.12.2-R0.1-SNAPSHOT',
exclude: [
'com.google.code.gson:gson',
'com.googlecode.json-simple:json-simple',
'commons-lang:commons-lang',
'org.yaml:snakeyaml'
]])
final def test = [
junit5: [
"org.junit.jupiter:junit-jupiter-api:$junit_version",
"org.junit.jupiter:junit-jupiter-engine:$junit_version"
],
mock : ['org.mockito:mockito-core:1.10.19'],
h2db : 'com.h2database:h2:1.4.200'
]
private static def filter(library) {
Object[] result = new Object[2]
result[0] = library.lib
result[1] = {
library.exclude.each { String excludeLibStr ->
String[] excludeLib = excludeLibStr.split(':', 2)
if (excludeLib.length == 2) {
exclude group: excludeLib[0], module: excludeLib[1]
} else {
exclude group: excludeLib[0]
}
}
}
return result
}
}

View File

@@ -0,0 +1,67 @@
import org.gradle.api.DefaultTask
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.tasks.TaskAction
import java.nio.charset.StandardCharsets
import java.nio.file.Path
class LogicPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
project.tasks.register('incrementVersion', IncrementVersion)
}
}
class IncrementVersion extends DefaultTask {
@TaskAction
def incrementVersion() {
String nextVer
if (project.hasProperty('nextVersion')) {
nextVer = project.property('nextVersion')
} else {
nextVer = nextVersion()
}
incrementReadmeVersion(nextVer)
incrementGradlePropsVersion(nextVer)
}
private void incrementReadmeVersion(String nextVersion) {
String nextVersion2 = nextVersion.replaceAll(/-/, '--')
Reader reader = this.getClass().getResource('README.SRC.MD').toURI().toURL().newReader()
File readmeFile = project.file(project.getRootDir().toPath().resolve('README.MD'))
readmeFile.withWriter { wr ->
reader.eachLine { line ->
wr << line.replaceAll('@VERSION@', nextVersion)
.replaceAll('@VERSION2@', nextVersion2)
wr << "\n"
}
}
}
private void incrementGradlePropsVersion(String nextVersion) {
Path gradlePropertiesPath = project.projectDir.toPath().resolve('gradle.properties');
Properties gradleProperties = new Properties()
gradleProperties.load(gradlePropertiesPath.newReader())
gradleProperties.setProperty('moduleVersion', nextVersion)
gradlePropertiesPath.withWriter(StandardCharsets.UTF_8.name(), { wr ->
gradleProperties.each {
wr << "${it.key}=${it.value}"
wr << "\n"
}
})
}
private String nextVersion() {
def matcher = project.property('moduleVersion') =~ /^(.*\d+\.)(\d+)(-.+)?$/
matcher.find()
return matcher.group(1) + (matcher.group(2).toInteger() + 1) + (matcher.group(3) == null ? '' : matcher.group(3))
}
}

View File

@@ -0,0 +1,636 @@
# GHAST TOOLS
![version: @VERSION@](https://img.shields.io/badge/version-@VERSION2@-0a0.svg?style=flat)
![bukkit-api: 1.12](https://img.shields.io/badge/bukkit--api-1.12-d50.svg?style=flat)
Набор вспомогательных инструментов для Bukkit API.
_Основан на версии Bukkit API 1.12._
## Содержание
1. [Перед использованием](#перед-использованием)
2. [GhastTools](#ghasttools)
3. [AssetsManager](#assetsmanager)
4. [BuildHelper](#buildhelper)
5. [CommandManager](#commandmanager)
6. [EffectsHelper](#effectshelper)
7. [EventContext](#eventcontext)
8. [JdbcTemplate](#jdbctemplate)
9. [ScheduleManager](#schedulemanager)
10. [ScheduleTask](#scheduletask)
11. [I18n](#i18n)
12. [XLog](#xlog)
13. [Подключение](#подключение)
1. [Gradle](#gradle)
2. [Maven](#maven)
---
## Перед использованием
Перед началом использования, необходимо в вашем Bukkit-плагине прописать подобный код:
```java
void onLoad() {
GhastTools.setPlugin(this);
}
```
Это необходимо сделать, т.к. весь инструментарий основан на статических (static) методах.
Все методы данного набора инструментов объеденены в классы, выполняющие роль группировщиков.
## GhastTools
Методы общего назначения или без определённой группировки.
### loadConfig
Загрузка файла настроек плагина - `config.yml`.
```java
YamlConfiguration config = GhastTools.loadConfig();
```
По-умолчанию "гаст" пытается файл найти в папке плагина - `getDataFolder()`.
Если файла там нет, то выгружает встроенный (имеющийся в `.jar` файле плагина) в эту папку и загружает его.
```java
YamlConfiguration config = GhastTools.loadConfig(false);
```
Если передать параметр `false`, то при отсутствии файла `config.yml` в папке плагина,
будет загруден исключительно встроенный файл настроек.
## AssetsManager
Методы по работе с файлами плагина (_"ассетами"_).
У каждой группы методов один и тот же набор входных параметров:
- `resourceName` - наименование и путь к файлу в папке плагина
- `defaultResourceName` - наименование и путь к файлу в плагине.
_Опционально. По-умолчанию равнен `resourceName`_
- `saveDefault` - необходимость скопировать содержимое файла из `defaultResourceName` в файл `resourceName`.
_Опционально. По-умолчанию равен `true`_
Правила поиска файлов так же одинаков для каждой группы:
- В начале файл ищется в папке плагина
- Если файл отсутствует в папке плагина, то...
- если `defaultResourceName` не равен `null`...
- если `saveDefault` равен `true`, то файл из плагина будет выгружен в папку плагина и от туда загружен в память.
- если `saveDefault` равен `false`, то данные будут взяты из файлв в плагине.
- если `defaultResourceName` равен `null`, то будет брошено исключение `AssetsException` с описанием ошибки вида "Файл X не найден".
### getAsInputStream
```java
InputStream inputStream = AssetsManager.getAsInputStream("translate.ru.yml", "translate.yml", false);
```
### getAsReader
```java
Reader reader = AssetsManager.getAsReader("translate.ru.yml", "translate.yml", false);
```
### getAsString
```java
String string = AssetsManager.getAsString("readme.txt", "readme.txt", StandardCharsets.UTF_8, false);
```
У этой группы есть дополнительный _опциональный_ параметр - `charset` - в котором указывается кодиривка получаемой строки.
о-умолчанию равен `StandardCharsets.UTF_8`_
## BuildHelper
Набор методов облегчающих размецение объектов на карте.
### placeSkull
```java
Location location = ...;
Skull skull = BuildHelper.placeSkull(location, BlockFace.NORTH)
```
### placeSignWall
```java
Location location = ...;
Sign signWall = BuildHelper.placeSignWall(location, BlockFace.NORTH)
```
## CommandManager
Регистрация команд.
Имеется два варианта использования: упрощённый
```java
CommandManager.register("start", (sender, args) -> sender.sendMessage("hello!"));
```
и подробный:
```java
CommandManager.create("start")
.useOnlyPlayer()
.executer((sender, args) -> sender.sendMessage("hello!"))
.register();
```
### register
Упрощенная регистрация команды. Указывается лишь название команды и исполнитель.
```java
CommandManager.register("start", (sender, args) -> sender.sendMessage("hello!"));
```
### create
Конструктор для подробного варианта регистрации команды.
```java
CommandManager.Builder builder = CommandManager.create("start");
```
### executer
Указание исполнителя для команды
```java
CommandManager.Builder builder = CommandManager.create("start")
.executer((sender, args) -> sender.sendMessage("hello!"));
```
### onError
Обработчик исключений
```java
CommandManager.Builder builder = CommandManager.create("start")
.onError((sender, commandName, args, exception) -> {
sender.sendMessage(ChatColor.RED + "Произошла ошибка при выполнении команды '" + commandName + "'.");
exception.printStackTrace();
});
```
### useOnlyPlayer
Указание, что данную команду могут использовать только Игроки.
Опционально можно указать сообщение, которое будет выводиться в консоль.
_Отменяет действие указателя `useOnlyConsole`_
```java
CommandManager.Builder builder = CommandManager.create("start")
.useOnlyPlayer("Команду могут использовать только игроки");
```
### useOnlyConsole
Указание, что данную команду можно использовать только в консоле.
Опционально можно указать сообщение, которое будет выводиться Игроку.
_Отменяет действие указателя `useOnlyPlayer`_
```java
CommandManager.Builder builder = CommandManager.create("start")
.useOnlyConsole(ChatColor.RED + "Команду можно использовать только в консоли");
```
### register
Регистрация описанной в Конструкторе команды.
```java
CommandManager.create("start")
.useOnlyPlayer()
.executer((sender, args) -> sender.sendMessage("hello!"))
.register();
```
## EffectsHelper
Набор методов для работы с эффектами.
### playSound
Воспроизвести звук.
```java
Location location = ...;
EffectsHelper.playSound(location, Sound.AMBIENT_CAVE, 1.0f);
```
### particle
Создание частиц.
```java
Location location = ...;
EffectsHelper.particle(location, Particle.REDSTONE, 1.0d, 1.0d, 1.0d, 1.0d, 5);
```
## EventContext
Регистрация группы обработчиков событий, объединённых общим условием выполнения.
```java
EventContext.create()
.filter(() -> Bukkit.getOnlinePlayers().size() > 10)
.onEvent(PlayerJoinEvent.class, event -> { event.getPlayer().kickPlayer("Max players"); });
```
### create
Создание контекста событий.
```java
EventContext eventContext = EventContext.create();
```
### filter
Условие, при котором будут срабатывать обработчики событий в данном контексте.
```java
EventContext.create()
.filter(() -> Bukkit.getOnlinePlayers().size() > 10)
```
### onEvent
Указание события и его обработчика.
_Обработчик события регистрируется сразу же._
```java
EventContext.create()
.filter(() -> Bukkit.getOnlinePlayers().size() > 10)
.onEvent(PlayerJoinEvent.class, event -> event.getPlayer().kickPlayer("Max players"));
```
### cancelEvent
Отменить событие.
_Обработчик события регистрируется сразу же._
```java
EventContext.create()
.cancelEvent(BlockPlaceEvent.class);
```
эквивалентен коду:
```java
EventContext.create()
.onEvent(BlockPlaceEvent.class, event -> event.setCancelled(true))
```
## JdbcTemplate
Инструмент для упрощения работы с SQL базами данных, работающими через JDBC.
Для начала потребуется создать объект `DataSource`
```java
// На примере MySQL
MysqlDataSource dataSource = new MysqlDataSource();
dataSource.setServerName("localhost");
dataSource.setPort(3306);
dataSource.setCharacterEncoding(StandardCharsets.UTF_8.name());
dataSource.setDatabaseName("MyDataBase");
dataSource.setUser("root");
dataSource.setPassword("secret");
```
После чего создать `JdbcTemplate`
```java
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
```
### execute
Выполнение SQL запроса без последующей обработки результатов выполнения.
```java
jdbcTemplate.execute("CREATE TABLE my_table (id int, name varchar(16));");
```
### query
Выполнение SQL запроса и обработка его результатов.
Может возвращать любой тип объектов.
```java
String name = jdbcTemplate.query("SELECT name FROM my_table LIMIT 0,1", resultSet -> {
if (resultSet.next()) {
return resultSet.getString("name");
} else {
return null;
}
});
```
```java
List<String> names = jdbcTemplate.query("SELECT name FROM my_table", resultSet -> {
if (resultSet.next()) {
List<String> list = new ArrayList<>();
do {
list.add(resultSet.getString("name"));
} while (resultSet.next());
return list;
} else {
return Collections.emptyList();
}
});
```
### queryOne
Выполнение SQL запроса с расчетом, что результат будет единичным либо не будет вовсе.
Возвращает `Optional`.
```java
Optional<String> optName = jdbcTemplate.queryOne("SELECT name FROM my_table WHERE name LIKE 'dmitriymx'", rs -> rs.getString("name"));
```
### queryList
Выполнение SQL запроса и обработка результата как списка данных.
Возвращает `List`.
```java
List<String> names = jdbcTemplate.queryList("SELECT name FROM my_table", (resultSet, rowNum) -> resultSet.getString("name"));
```
### queryForMap
Выполнение SQL запроса с расчетом, что результат будет единичным либо не будет вовсе.
Возвращает `Map<String, Object>`, где ключ — это наименование колонок таблицы, а значения — это значения в ячейках таблицы.
```java
Map<String, Object> map = jdbcTemplate.queryForMap("SELECT * FROM my_table LIMIT 0,1;");
```
### queryForMapList
Выполнение SQL запроса и обработка результата как списка данных.
Возвращает `List<Map<String, Object>>`, где ключ — это наименование колонок таблицы, а значения — это значения в ячейках таблицы.
```java
List<Map<String, Object>> mapList = jdbcTemplate.queryForMapList("SELECT * FROM my_table");
```
### update
Выполнение SQL запроса где будет происходить обновление данных в таблице.
Под "обновлением" подразумеваются любые изменения в таблице: `UPDATE`, `DELETE`, `INSERT`.
Возвращает число строк, которые были _по факту обновлены_ в таблице.
```java
int rows = jdbcTemplate.update("DELETE FROM my_table WHERE name LIKE 'dmitriymx';");
```
## ScheduleManager
Набор методов для создания параллельных задач, выполняющихся один раз или по рассписанию.
### createTask
Создание конструктора задачи.
```java
ScheduleManager.Builder builder = ScheduleManager.createTask();
```
### useBukkitScheduler
Если задача будет взаимодействоватьс **Bukkit API** или необходима привязка задачи к _тикам_,
то необходимо использовать данный указатель.
В ином случае, указатель не нужен.
```java
ScheduleManager.Builder builder = ScheduleManager.createTask()
.useBukkitScheduler();
```
### after
Указание, что задачу нужно выполнить не сразу, а с некоторой задержкой перед запуском.
```java
ScheduleManager.Builder builder = ScheduleManager.createTask()
.after(5, TimeUnit.MINUTES);
```
### every
Указание, что задачу нужно повторять через указанное время.
```java
ScheduleManager.Builder builder = ScheduleManager.createTask()
.every(5, TimeUnit.MINUTES);
```
### create
Создание описанной задачи.
```java
ScheduleTask scheduleTask = ScheduleManager.createTask()
.every(1, TimeUnit.SECONDS)
.create(() -> Bukkit.getServer().getLogger().info("TimeMS: " + System.currentTimeMillis()));
```
_Задача будет только создана. Для её выполнения нужно вызвать `scheduleTask.start()`._
### execute
Создание и выполнение описанной задачи.
```java
ScheduleTask scheduleTask = ScheduleManager.createTask()
.every(1, TimeUnit.SECONDS)
.execute(() -> Bukkit.getServer().getLogger().info("TimeMS: " + System.currentTimeMillis()));
```
## ScheduleTask
Вспомогательный объект, созданный через `ScheduleManager`. Позволяет управлять созданной задачей.
### start
Запускает задачу, если она еще не запущена.
```java
ScheduleTask scheduleTask = ...;
scheduleTask.start();
```
### isCanceled
Возвращает состояние задачи. Если `true`, значит задача была или _отменена/остоновлена_ или была завершена.
```java
ScheduleTask scheduleTask = ...;
boolean status = scheduleTask.isCanceled();
```
### cancel
Отменяет/Остонавливает выполнение задачи.
```java
ScheduleTask scheduleTask = ...;
scheduleTask.cancel();
```
## I18n
Инструмент для работы с мультиязыковыми сообщениями или просто сообщениями, которые храняться в отдельном файле.
Позволяет использовать шаблонизированные сообщения вида `Привет, {player}!`.
### loadMessages
Загрузка сообщений в инструмент.
Передать можно как "мапу" с перечислением ключ-сообщение, так и `Reader` на файл в формате `key=message` (как у `Properties`).
```java
Map<String, String> messagesMap = ...;
I18n.loadMessages(messagesMap);
```
```java
Reader reader = AssetsManager.getAsReader("messages.properties");
I18n.loadMessages(reader);
```
В первом параметре можно указать код языка, для которого загружаются сообщения. По-умолчанию будет "en".
```java
Map<String, String> enMessagesMap = ...;
Map<String, String> ruMessagesMap = ...;
I18n.loadMessages("en", enMessagesMap);
I18n.loadMessages("ru", ruMessagesMap);
```
```java
Reader readerEn = AssetsManager.getAsReader("messages.properties");
Reader readerRu = AssetsManager.getAsReader("messages.ru.properties");
I18n.loadMessages("en", readerEn);
I18n.loadMessages("ru", readerRu);
```
### get
Получение сообщения по его ключу.
```java
String msg = I18n.get("player.join.msg");
```
Если следующим сообщением указать `Map<String, Object>`, то можно будет воспользоваться шаблонизатором.
```java
Map<String, String> messagesMap = new HashMap<>();
messagesMap.put("player.join.msg", "Привет, {player}!");
I18n.loadMessages(messagesMap);
Map<String, String> params = new HashMap<>();
params.put("player", event.getPlayer().getName());
String msg = I18n.get("player.join.msg", params);
```
Однако можно создавать `Map<String, String> params` явно, а воспользоваться [paramBuilder()](#parambuilder)
```java
String msg = I18n.get("player.join.msg", I18n.paramBuilder()
.add("player", event.getPlayer().getName())
.build());
```
Можно первым параметром указать код языка.
```java
String msg = I18n.get("ru", "player.join.msg");
```
### paramBuilder
Инструмент для параметизирования шаблонов сообщений.
```java
Map<String, Object> params = I18n.paramBuilder()
.add("player", event.getPlayer().getName())
.build();
String msg = I18n.get("player.join.msg", params);
```
## XLog
Замена стандартному `getLogger()`, который использует `java.utils.Logger` и не всегда удобен для логирования.
Имеет 4 уровня логирования: `debug`, `info`, `warning`, `error`.
Сообщения могут быть шаблонизированными. Синтаксис шаблонов — `java.text.MessageFormat`.
Примеры:
```java
XLog.info("Hello");
XLog.info("Player {0} join game", event.getPlayer().getName());
XLog.error("ERROR!", exception);
XLog.error("ERROR: {0}", exception.getMessage());
// Для экранирования "{" нужно перед ней поставить "'".
// А для использования "'" нужно их дублировать.
XLog.error("ERROR ''{0}'' in Event '{{1}'}: {2}", exception.getClass(), event.getClass(), exception.getMessage());
XLog.error("ERROR: {0}", exception.getMessage(), exception);
```
---
## Подключение
### Gradle
```groovy
repositories {
maven { url 'https://dmx-mc-project.gitlab.io/maven-repository/' }
}
```
```groovy
implementation group: 'ghast', name: 'ghast-tools', version: '@VERSION@'
```
### Maven
```xml
<repositories>
<repository>
<id>dmx-mc-project</id>
<url>https://dmx-mc-project.gitlab.io/maven-repository/</url>
</repository>
</repositories>
```
```xml
<dependencies>
<dependency>
<groupId>ghast</groupId>
<artifactId>ghast-tools</artifactId>
<version>@VERSION@</version>
</dependency>
</dependencies>
```

View File

@@ -1,3 +1 @@
projectGroup=ghast projectGroup=ghast
projectName=ghast-tools
projectVersion=1.7

View File

@@ -0,0 +1,3 @@
dependencies {
compileOnly libs.bukkit
}

View File

@@ -0,0 +1,2 @@
moduleName=phantom-classes
moduleVersion=1.0-SNAPSHOT

View File

@@ -0,0 +1,16 @@
package com.mojang.authlib;
import com.mojang.authlib.properties.PropertyMap;
import java.util.UUID;
public class GameProfile {
public GameProfile(UUID id, String name) {
}
public PropertyMap getProperties() {
return null;
}
}

View File

@@ -0,0 +1,8 @@
package com.mojang.authlib.properties;
public class Property {
public Property(String value, String name) {
}
}

View File

@@ -0,0 +1,12 @@
package com.mojang.authlib.properties;
import com.google.common.collect.ForwardingMultimap;
import com.google.common.collect.Multimap;
public class PropertyMap extends ForwardingMultimap<String, Property> {
@Override
protected Multimap<String, Property> delegate() {
return null;
}
}

View File

@@ -0,0 +1,7 @@
package net.minecraft.server.v1_12_R1;
public class BlockPosition {
public BlockPosition(double x, double y, double z) {
}
}

View File

@@ -0,0 +1,5 @@
package net.minecraft.server.v1_12_R1;
public class TileEntity {
}

View File

@@ -0,0 +1,10 @@
package net.minecraft.server.v1_12_R1;
import com.mojang.authlib.GameProfile;
public class TileEntitySkull extends TileEntity {
public void setGameProfile(GameProfile gameprofile) {
}
}

View File

@@ -0,0 +1,8 @@
package net.minecraft.server.v1_12_R1;
public class WorldServer {
public TileEntity getTileEntity(BlockPosition pos) {
return null;
}
}

View File

@@ -0,0 +1,10 @@
package org.bukkit.craftbukkit.v1_12_R1;
import org.bukkit.command.SimpleCommandMap;
public class CraftServer {
public SimpleCommandMap getCommandMap() {
return null;
}
}

View File

@@ -0,0 +1,10 @@
package org.bukkit.craftbukkit.v1_12_R1;
import net.minecraft.server.v1_12_R1.WorldServer;
public class CraftWorld {
public WorldServer getHandle() {
return null;
}
}

View File

@@ -1,2 +1,2 @@
rootProject.name = projectName include 'phantom-classes'
include 'tools'

View File

@@ -1,36 +0,0 @@
package ghast;
import lombok.experimental.UtilityClass;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.Sign;
import org.bukkit.block.Skull;
@UtilityClass
@SuppressWarnings("unused")
public class BuildHelper {
public Skull placeSkull(Location location, BlockFace face) {
Block block = location.getWorld().getBlockAt(location);
block.setType(Material.SKULL);
Skull skull = (Skull) block.getState();
skull.setRotation(face);
org.bukkit.material.Skull skullMaterial = (org.bukkit.material.Skull) skull.getData();
skullMaterial.setFacingDirection(BlockFace.SELF);
return skull;
}
public Sign placeSignWall(Location location, BlockFace face) {
Block block = location.getWorld().getBlockAt(location);
block.setType(Material.WALL_SIGN);
Sign sign = (Sign) block.getState();
org.bukkit.material.Sign signMaterial = (org.bukkit.material.Sign) sign.getData();
signMaterial.setFacingDirection(face);
return sign;
}
}

13
tools/build.gradle Normal file
View File

@@ -0,0 +1,13 @@
repositories {
maven repos.mavenSpigotMC
maven repos.mavenDmxMcProject
}
dependencies {
compileOnly project(':phantom-classes')
compileOnly libs.bukkit
implementation libs.commons_text
testImplementation libs.bukkit
testImplementation libs.test.h2db
}

2
tools/gradle.properties Normal file
View File

@@ -0,0 +1,2 @@
moduleName=ghast-tools
moduleVersion=1.11-SNAPSHOT

View File

@@ -0,0 +1,123 @@
package ghast;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import lombok.experimental.UtilityClass;
import net.minecraft.server.v1_12_R1.BlockPosition;
import net.minecraft.server.v1_12_R1.TileEntitySkull;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.SkullType;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.Sign;
import org.bukkit.block.Skull;
import org.bukkit.craftbukkit.v1_12_R1.CraftWorld;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.UUID;
@UtilityClass
@SuppressWarnings("unused")
public class BuildHelper {
/**
* Установка черепа.
* <p>
* После установки, необходимо выполнить <code>skull.update(true);</code>
*
* @param location место установки.
* @param face куда будет повёрнут череп.
* @return Блок типа {@link Skull}
*/
public Skull placeSkull(Location location, BlockFace face) {
Location fixedLocation = GhastTools.copyLocation(location);
fixedLocation.setZ(fixedLocation.getZ() - 1);
Block block = location.getWorld().getBlockAt(location);
block.setType(Material.SKULL);
Skull skull = (Skull) block.getState();
skull.setRotation(face);
org.bukkit.material.Skull skullMaterial = (org.bukkit.material.Skull) skull.getData();
skullMaterial.setFacingDirection(BlockFace.SELF);
return skull;
}
/**
* Установка головы игрока.
* <p>
* После установки, необходимо выполнить <code>skull.update(true);</code>
*
* @param location место установки.
* @param face куда будет повёрнута голова.
* @return Блок типа {@link Skull}
*/
public static Skull placePlayerHead(Location location, BlockFace face) {
Location fixedLocation = GhastTools.copyLocation(location);
fixedLocation.setZ(fixedLocation.getZ() - 1);
Block block = fixedLocation.getWorld().getBlockAt(fixedLocation);
block.setType(Material.SKULL);
Skull skull = (Skull) block.getState();
skull.setSkullType(SkullType.PLAYER);
skull.setRotation(face);
org.bukkit.material.Skull skullMaterial = (org.bukkit.material.Skull) skull.getData();
skullMaterial.setFacingDirection(BlockFace.SELF);
return skull;
}
/**
* Установка текстурированной головы игрока.
*
* @param location место установки.
* @param face куда будет повёрнута голова.
* @param skinUrl URL на текстуру
* @return Блок типа {@link Skull}
*/
public static Skull placePlayerHead(Location location, BlockFace face, String skinUrl) {
Skull playerHead = placePlayerHead(location, face);
playerHead.update(true);
setPlayerHeadSkin(playerHead, skinUrl);
return playerHead;
}
/**
* Установка текстуры для головы игрока.
*
* @param skull блок головы игрока
* @param skinUrl URL на текстуру
*/
public static void setPlayerHeadSkin(Skull skull, String skinUrl) {
((TileEntitySkull) (((CraftWorld) skull.getWorld())
.getHandle()
.getTileEntity(new BlockPosition(skull.getX(), skull.getY(), skull.getZ()))))
.setGameProfile(generateTexturedPlayerProfile(skinUrl));
}
public Sign placeSignWall(Location location, BlockFace face) {
Block block = location.getWorld().getBlockAt(location);
block.setType(Material.WALL_SIGN);
Sign sign = (Sign) block.getState();
org.bukkit.material.Sign signMaterial = (org.bukkit.material.Sign) sign.getData();
signMaterial.setFacingDirection(face);
return sign;
}
private GameProfile generateTexturedPlayerProfile(String skinUrl) {
GameProfile gameProfile = new GameProfile(UUID.randomUUID(), null);
gameProfile.getProperties().put("textures",
new Property("textures", Base64.getEncoder().encodeToString(
("{textures:{SKIN:{url:\"" + skinUrl + "\"}}}").getBytes(StandardCharsets.UTF_8))));
return gameProfile;
}
}

View File

@@ -1,6 +1,8 @@
package ghast; package ghast;
import ghast.assets.AssetsManager;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
import org.bukkit.Location;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
@@ -53,4 +55,8 @@ public class GhastTools {
public YamlConfiguration loadConfig() { public YamlConfiguration loadConfig() {
return loadConfig(true); return loadConfig(true);
} }
public Location copyLocation(Location location) {
return new Location(location.getWorld(), location.getX(), location.getY(), location.getZ());
}
} }

View File

@@ -1,4 +1,4 @@
package ghast; package ghast.assets;
public class AssetsException extends RuntimeException { public class AssetsException extends RuntimeException {

View File

@@ -1,5 +1,6 @@
package ghast; package ghast.assets;
import ghast.GhastTools;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;

View File

@@ -4,8 +4,7 @@ import lombok.AccessLevel;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.command.Command; import org.bukkit.craftbukkit.v1_12_R1.CraftServer;
import ru.dmitriymx.reflection.ReflectionObject;
@UtilityClass @UtilityClass
@SuppressWarnings("unused") @SuppressWarnings("unused")
@@ -59,12 +58,9 @@ public class CommandManager {
} }
public void register() { public void register() {
//TODO для Paper такие "извращения" не требуются. Нужно продумать. ((CraftServer) Bukkit.getServer()).getCommandMap().register(
new ReflectionObject(Bukkit.getServer()) name,
.method("getCommandMap").invoke() new CommandWrapper(name, this.onlyPlayer, this.deniedMessage, this.executer, this.errorConsumer)
.method("register", String.class, Command.class).invoke(
name, new CommandWrapper(name, this.onlyPlayer, this.deniedMessage,
this.executer, this.errorConsumer)
); );
} }
} }

View File

@@ -0,0 +1,10 @@
package ghast.database;
import java.sql.SQLException;
public class CannotGetJdbcConnectionException extends DataAccessException {
public CannotGetJdbcConnectionException(String msg, SQLException ex) {
super(msg, ex);
}
}

View File

@@ -0,0 +1,19 @@
package ghast.database;
import lombok.Getter;
@SuppressWarnings("java:S1165")
@Getter
public class DataAccessException extends RuntimeException {
private String sql;
public DataAccessException(String msg, Throwable cause) {
super(msg, cause);
}
public DataAccessException(String msg, String sql, Throwable cause) {
this(msg, cause);
this.sql = sql;
}
}

View File

@@ -0,0 +1,22 @@
package ghast.database;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public interface JdbcOperations {
void execute(String sql) throws DataAccessException;
<T> T query(String sql, ResultSetExtractor<T> rse) throws DataAccessException;
<T> Optional<T> queryOne(String sql, ResultSetExtractor<T> rse) throws DataAccessException;
<T> List<T> queryList(String sql, RowMapper<T> rowMapper) throws DataAccessException;
Map<String, Object> queryForMap(String sql) throws DataAccessException;
List<Map<String, Object>> queryForMapList(String sql) throws DataAccessException;
int update(String sql) throws DataAccessException;
}

View File

@@ -0,0 +1,244 @@
package ghast.database;
import ghast.XLog;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.sql.DataSource;
import java.sql.Date;
import java.sql.*;
import java.util.*;
@NoArgsConstructor
@Getter
@Setter
public class JdbcTemplate implements JdbcOperations {
private static final String DBG_SQL_INFO = "Execute SQL: {0}";
private DataSource dataSource;
public JdbcTemplate(DataSource dataSource) {
setDataSource(dataSource);
}
@Override
public void execute(String sql) throws DataAccessException {
XLog.debug(DBG_SQL_INFO, sql);
Connection connection = openConnection();
Statement statement = null;
try {
statement = connection.createStatement();
statement.execute(sql);
} catch (SQLException e) {
throw new DataAccessException("Error execute SQL", sql, e);
} finally {
closeStatement(statement);
closeConnection(connection);
}
}
@Override
public <T> T query(String sql, ResultSetExtractor<T> rse) throws DataAccessException {
XLog.debug(DBG_SQL_INFO, sql);
Connection connection = openConnection();
Statement statement = null;
ResultSet resultSet = null;
try {
statement = connection.createStatement();
resultSet = statement.executeQuery(sql);
return rse.extractData(resultSet);
} catch (SQLException e) {
throw new DataAccessException("Error execute SQL", sql, e);
} finally {
closeResultSet(resultSet);
closeStatement(statement);
closeConnection(connection);
}
}
@Override
public <T> Optional<T> queryOne(String sql, ResultSetExtractor<T> rse) throws DataAccessException {
return query(sql, rs -> {
if (rs.next()) {
return Optional.ofNullable(rse.extractData(rs));
} else {
return Optional.empty();
}
});
}
@Override
public <T> List<T> queryList(String sql, final RowMapper<T> rowMapper) throws DataAccessException {
return query(sql, rs -> {
List<T> resultList;
int rowNum = 0;
if (rs.next()) {
resultList = new ArrayList<>();
do {
resultList.add(rowMapper.mapRow(rs, rowNum++));
} while (rs.next());
} else {
resultList = Collections.emptyList();
}
return resultList;
});
}
@Override
public Map<String, Object> queryForMap(String sql) throws DataAccessException {
return query(sql, rs -> {
if (rs.next()) {
ResultSetMetaData metaData = rs.getMetaData();
int columnCount = metaData.getColumnCount();
return rowToMap(columnCount, metaData, rs);
} else {
return Collections.emptyMap();
}
});
}
@Override
public List<Map<String, Object>> queryForMapList(String sql) throws DataAccessException {
return query(sql, rs -> {
List<Map<String, Object>> resultList;
if (rs.next()) {
resultList = new ArrayList<>();
ResultSetMetaData metaData = rs.getMetaData();
int columnCount = metaData.getColumnCount();
do {
resultList.add(rowToMap(columnCount, metaData, rs));
} while (rs.next());
} else {
resultList = Collections.emptyList();
}
return resultList;
});
}
@Override
public int update(String sql) throws DataAccessException {
XLog.debug(DBG_SQL_INFO, sql);
Connection connection = openConnection();
Statement statement = null;
try {
statement = connection.createStatement();
int rows = statement.executeUpdate(sql);
XLog.debug("Affected {0} rows", rows);
return rows;
} catch (SQLException e) {
XLog.error("Error execute SQL: {0}", e.getMessage(), e);
return 0;
} finally {
closeStatement(statement);
closeConnection(connection);
}
}
private Connection openConnection() {
try {
return getDataSource().getConnection();
} catch (SQLException ex) {
throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex);
}
}
private String lookupColumnName(ResultSetMetaData resultSetMetaData, int columnIndex) throws SQLException {
String name = resultSetMetaData.getColumnLabel(columnIndex);
if (name == null || name.isEmpty()) {
name = resultSetMetaData.getColumnName(columnIndex);
}
return name;
}
private Map<String, Object> rowToMap(int columnCount, ResultSetMetaData metaData, ResultSet rs) throws SQLException {
Map<String, Object> rowMap = new LinkedHashMap<>(columnCount);
for (int i = 1; i <= columnCount; i++) {
String key = lookupColumnName(metaData, i);
Object value = getResultSetRawValue(rs, i);
rowMap.put(key, value);
}
return rowMap;
}
private Object getResultSetRawValue(ResultSet resultSet, int index) throws SQLException {
Object obj = resultSet.getObject(index);
if (obj == null) {
return null;
}
String className = obj.getClass().getName();
if (obj instanceof Blob) {
Blob blob = (Blob) obj;
obj = blob.getBytes(1, (int) blob.length());
} else if (obj instanceof Clob) {
Clob clob = (Clob) obj;
obj = clob.getSubString(1, (int) clob.length());
} else if ("oracle.sql.TIMESTAMP".equals(className) || "oracle.sql.TIMESTAMPTZ".equals(className)) {
obj = resultSet.getTimestamp(index);
} else if (className.startsWith("oracle.sql.DATE")) {
String metaDataClassName = resultSet.getMetaData().getColumnClassName(index);
if ("java.sql.Timestamp".equals(metaDataClassName) || "oracle.sql.TIMESTAMP".equals(metaDataClassName)) {
obj = resultSet.getTimestamp(index);
} else {
obj = resultSet.getDate(index);
}
} else if (obj instanceof Date
&& "java.sql.Timestamp".equals(resultSet.getMetaData().getColumnClassName(index))) {
obj = resultSet.getTimestamp(index);
}
return obj;
}
private void closeResultSet(ResultSet resultSet) {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
XLog.debug("Could not close JDBC ResultSet", e);
} catch (Exception e) {
XLog.debug("Unexpected exception on closing JDBC ResultSet", e);
}
}
}
private void closeStatement(Statement statement) {
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
XLog.debug("Could not close JDBC Statement", e);
} catch (Exception e) {
XLog.debug("Unexpected exception on closing JDBC Statement", e);
}
}
}
private void closeConnection(Connection con) {
if (con == null) {
return;
}
try {
con.close();
} catch (SQLException e) {
XLog.debug("Could not close JDBC Connection", e);
} catch (Exception e) {
XLog.debug("Unexpected exception on closing JDBC Connection", e);
}
}
}

View File

@@ -0,0 +1,9 @@
package ghast.database;
import java.sql.ResultSet;
import java.sql.SQLException;
public interface ResultSetExtractor<T> {
T extractData(ResultSet rs) throws SQLException, DataAccessException;
}

View File

@@ -0,0 +1,9 @@
package ghast.database;
import java.sql.ResultSet;
import java.sql.SQLException;
public interface RowMapper<T> {
T mapRow(ResultSet rs, int rowNum) throws SQLException;
}

View File

@@ -0,0 +1,33 @@
package ghast.scheduler;
import lombok.RequiredArgsConstructor;
import org.bukkit.scheduler.BukkitTask;
import java.util.function.Supplier;
@RequiredArgsConstructor
public class BukkitScheduleTask implements ScheduleTask {
private final Supplier<BukkitTask> generator;
private BukkitTask bukkitTask;
@Override
public void start() {
if (isCanceled()) {
bukkitTask = generator.get();
}
}
@Override
public boolean isCanceled() {
return bukkitTask == null || bukkitTask.isCancelled();
}
@Override
public void cancel() {
if (bukkitTask != null) {
bukkitTask.cancel();
bukkitTask = null;
}
}
}

View File

@@ -0,0 +1,33 @@
package ghast.scheduler;
import lombok.RequiredArgsConstructor;
import java.util.concurrent.Future;
import java.util.function.Supplier;
@RequiredArgsConstructor
public class JavaScheduleTask implements ScheduleTask {
private final Supplier<Future<?>> generator;
private Future<?> future;
@Override
public void start() {
if (future == null || future.isDone()) {
future = generator.get();
}
}
@Override
public boolean isCanceled() {
return future == null || future.isCancelled();
}
@Override
public void cancel() {
if (future != null) {
future.cancel(true);
future = null;
}
}
}

View File

@@ -1,6 +1,7 @@
package ghast; package ghast.scheduler;
import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.common.util.concurrent.ThreadFactoryBuilder;
import ghast.GhastTools;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
@@ -45,48 +46,68 @@ public class ScheduleManager {
return this; return this;
} }
public void execute(Runnable runnable) { public ScheduleTask create(Runnable runnable) {
if (useBukkitScheduler) { if (useBukkitScheduler) {
createBukkitSchedule(runnable); return createBukkitSchedule(runnable);
} else { } else {
createSchedule(runnable); return createSchedule(runnable);
} }
} }
private void createBukkitSchedule(Runnable runnable) { public ScheduleTask execute(Runnable runnable) {
ScheduleTask scheduleTask = create(runnable);
scheduleTask.start();
return scheduleTask;
}
private ScheduleTask createBukkitSchedule(Runnable runnable) {
BukkitScheduler bukkitScheduler = Bukkit.getScheduler(); BukkitScheduler bukkitScheduler = Bukkit.getScheduler();
BukkitScheduleTask resultTask;
if (this.afterMs == null && this.everyMs == null) { if (this.afterMs == null && this.everyMs == null) {
bukkitScheduler.runTask(GhastTools.getPlugin(), runnable); resultTask = new BukkitScheduleTask(() -> bukkitScheduler.runTask(GhastTools.getPlugin(), runnable));
} else if (this.everyMs != null) { } else if (this.everyMs != null) {
long everyTicks = this.everyMs / MS_PER_ONE_TICK; long everyTicks = this.everyMs / MS_PER_ONE_TICK;
long afterTicks = this.afterMs != null ? this.afterMs / MS_PER_ONE_TICK : 0; long afterTicks = this.afterMs != null ? this.afterMs / MS_PER_ONE_TICK : 0;
bukkitScheduler.runTaskTimer(GhastTools.getPlugin(), runnable, afterTicks, everyTicks); resultTask = new BukkitScheduleTask(() ->
bukkitScheduler.runTaskTimer(GhastTools.getPlugin(), runnable, afterTicks, everyTicks));
} else { } else {
long ticks = this.afterMs / MS_PER_ONE_TICK; long ticks = this.afterMs / MS_PER_ONE_TICK;
bukkitScheduler.runTaskLater(GhastTools.getPlugin(), runnable, ticks); resultTask = new BukkitScheduleTask(() ->
} bukkitScheduler.runTaskLater(GhastTools.getPlugin(), runnable, ticks));
} }
private void createSchedule(Runnable runnable) { return resultTask;
}
private ScheduleTask createSchedule(Runnable runnable) {
ExecutorService executorService; ExecutorService executorService;
JavaScheduleTask resultTask;
if (this.afterMs == null && this.everyMs == null) { if (this.afterMs == null && this.everyMs == null) {
executorService = Executors.newSingleThreadExecutor(THREAD_FACTORY); executorService = Executors.newSingleThreadExecutor(THREAD_FACTORY);
executorService.execute(runnable); resultTask = new JavaScheduleTask(() -> executorService.submit(runnable));
} else if (this.everyMs != null) { } else if (this.everyMs != null) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1, THREAD_FACTORY); ScheduledExecutorService scheduledExecutorService
= Executors.newScheduledThreadPool(1, THREAD_FACTORY);
resultTask = new JavaScheduleTask(() ->
scheduledExecutorService.scheduleAtFixedRate(runnable, scheduledExecutorService.scheduleAtFixedRate(runnable,
this.afterMs != null ? this.afterMs : 0, this.afterMs != null ? this.afterMs : 0,
everyMs, TimeUnit.MILLISECONDS); everyMs, TimeUnit.MILLISECONDS));
executorService = scheduledExecutorService; executorService = scheduledExecutorService;
} else { } else {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1, THREAD_FACTORY); ScheduledExecutorService scheduledExecutorService
scheduledExecutorService.schedule(runnable, afterMs, TimeUnit.MILLISECONDS); = Executors.newScheduledThreadPool(1, THREAD_FACTORY);
resultTask = new JavaScheduleTask(() ->
scheduledExecutorService.schedule(runnable, afterMs, TimeUnit.MILLISECONDS));
executorService = scheduledExecutorService; executorService = scheduledExecutorService;
} }
executorService.shutdown(); executorService.shutdown();
return resultTask;
} }
} }
} }

View File

@@ -0,0 +1,10 @@
package ghast.scheduler;
public interface ScheduleTask {
void start();
boolean isCanceled();
void cancel();
}

View File

@@ -0,0 +1,289 @@
package ghast.database;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import ghast.GhastTools;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.bukkit.plugin.Plugin;
import org.h2.jdbcx.JdbcDataSource;
import org.junit.jupiter.api.*;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.MessageFormat;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.StringJoiner;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
class JdbcTemplateTest {
static final String JDBC_USER = "sa";
static final String JDBC_PASSWORD = "";
static final String JDBC_DB_NAME = "in_mem_db";
static final String JDBC_URL = "jdbc:h2:mem:" + JDBC_DB_NAME + ";DB_CLOSE_DELAY=-1";
static final String TABLE_NAME = "TEST_TABLE";
static final String COLUMN_ID = "ID";
static final String COLUMN_NAME = "C_NAME";
static final String COLUMN_VALUE = "C_VALUE";
static final Object[][] DATA = new Object[][]{
{ "Player 1", 100 }, { "Player 2", 250 },
{ "Player 3", 0 }, { "Player 4", 780 }
};
static DataSource dataSource;
JdbcTemplate jdbcTemplate;
@BeforeAll
static void beforeAll() {
Logger logger = Logger.getLogger(JdbcTemplateTest.class.getName());
Plugin mockPlugin = mock(Plugin.class);
when(mockPlugin.getLogger()).thenReturn(logger);
GhastTools.setPlugin(mockPlugin);
JdbcDataSource jdbcDataSource = new JdbcDataSource();
jdbcDataSource.setUser(JDBC_USER);
jdbcDataSource.setPassword(JDBC_PASSWORD);
jdbcDataSource.setURL(JDBC_URL);
dataSource = jdbcDataSource;
}
@BeforeEach
void before() {
jdbcTemplate = new JdbcTemplate(dataSource);
createTable();
String sql_head = MessageFormat.format("INSERT INTO {0} ({1}, {2}) VALUES ",
TABLE_NAME, COLUMN_NAME, COLUMN_VALUE);
StringJoiner sql_sj = new StringJoiner(", ");
for (Object[] datum : DATA) {
sql_sj.add(MessageFormat.format("( ''{0}'', {1} )", datum[0], datum[1]));
}
jdbcTemplate.execute(sql_head + sql_sj.toString());
}
@AfterEach
void after() {
dropTable();
}
@Test
void testQuery_Simple_Single() {
String sql = MessageFormat.format("SELECT {2} FROM {0} WHERE {1} LIKE ''{3}''",
TABLE_NAME, COLUMN_NAME, COLUMN_VALUE, DATA[0][0]);
Integer value = jdbcTemplate.query(sql, rs -> {
if (rs.next()) {
return rs.getInt(1);
} else {
return null;
}
});
assertEquals(DATA[0][1], value);
}
@Test
void testQuery_Simple_Optional() {
String sql = MessageFormat.format("SELECT {2} FROM {0} WHERE {1} LIKE ''{3}''",
TABLE_NAME, COLUMN_NAME, COLUMN_VALUE, DATA[0][0]);
Optional<Integer> optValue = jdbcTemplate.queryOne(sql, rs -> rs.getInt(1));
assertTrue(optValue.isPresent());
assertEquals(DATA[0][1], optValue.get());
}
@Test
void testQuery_Simple_List() {
String sql = MessageFormat.format("SELECT {2} FROM {0} WHERE {1} LIKE ''{3}'' OR {1} LIKE ''{4}''",
TABLE_NAME, COLUMN_NAME, COLUMN_VALUE, DATA[0][0], DATA[1][0]);
List<Integer> listValues = jdbcTemplate.queryList(sql, (rs, rowNum) -> rs.getInt(1));
assertIterableEquals(Lists.newArrayList(DATA[0][1], DATA[1][1]), listValues);
}
@Test
void testQuery_Object_Single() {
String sql = MessageFormat.format("SELECT {1}, {2} FROM {0} WHERE {1} LIKE ''{3}''",
TABLE_NAME, COLUMN_NAME, COLUMN_VALUE, DATA[0][0]);
Player actualPlayer = jdbcTemplate.query(sql, rs -> {
if (rs.next()) {
Player player0 = new Player();
player0.name = rs.getString(COLUMN_NAME);
player0.value = rs.getInt(COLUMN_VALUE);
return player0;
} else {
return null;
}
});
Player expectedPlayer = new Player();
expectedPlayer.name = (String) DATA[0][0];
expectedPlayer.value = (int) DATA[0][1];
assertEquals(expectedPlayer, actualPlayer);
}
@Test
void testQuery_Object_List() {
String sql = MessageFormat.format("SELECT {1}, {2} FROM {0}",
TABLE_NAME, COLUMN_NAME, COLUMN_VALUE);
List<Player> actualPlayers = jdbcTemplate.queryList(sql, (rs, num) -> {
Player player0 = new Player();
player0.name = rs.getString(COLUMN_NAME);
player0.value = rs.getInt(COLUMN_VALUE);
return player0;
});
List<Player> expectedPlayers = Stream.of(DATA)
.map(datum -> {
Player player1 = new Player();
player1.name = (String) datum[0];
player1.value = (int) datum[1];
return player1;
})
.collect(Collectors.toList());
assertIterableEquals(expectedPlayers, actualPlayers);
}
@Test
void testQueryForMap() {
String sql = MessageFormat.format("SELECT {1}, {2} FROM {0} WHERE {1} LIKE ''{3}''",
TABLE_NAME, COLUMN_NAME, COLUMN_VALUE, DATA[0][0]);
Map<String, Object> actualMap = jdbcTemplate.queryForMap(sql);
Map<String, Object> expectedMap = ImmutableMap.of(
COLUMN_NAME, DATA[0][0],
COLUMN_VALUE, DATA[0][1]);
assertIterableEquals(expectedMap.entrySet(), actualMap.entrySet());
}
@Test
void testQueryForMapList() {
String sql = MessageFormat.format("SELECT {1}, {2} FROM {0}",
TABLE_NAME, COLUMN_NAME, COLUMN_VALUE);
List<Map<String, Object>> actualMapList = jdbcTemplate.queryForMapList(sql);
List<Map<String, Object>> expectedMapList = Stream.of(DATA)
.map(datum -> ImmutableMap.of(COLUMN_NAME, datum[0], COLUMN_VALUE, datum[1]))
.collect(Collectors.toList());
assertIterableEquals(expectedMapList, actualMapList);
}
@Test
void testUpdate() {
String newName = "Player X";
String sql = MessageFormat.format("UPDATE {0} SET {1} = ''{3}'' WHERE {1} LIKE ''{2}''",
TABLE_NAME, COLUMN_NAME, DATA[0][0], newName);
int rows = jdbcTemplate.update(sql);
assertEquals(1, rows);
sql = MessageFormat.format("SELECT {2} FROM {0} WHERE {1} LIKE ''{3}''",
TABLE_NAME, COLUMN_NAME, COLUMN_VALUE, newName);
Integer value = jdbcTemplate.query(sql, rs -> {
if (rs.next()) {
return rs.getInt(1);
} else {
return null;
}
});
assertEquals(DATA[0][1], value);
}
private void createTable() {
jdbcTemplate.execute("CREATE TABLE " + TABLE_NAME + " (" +
COLUMN_ID + " bigint auto_increment," +
COLUMN_NAME + " varchar(16)," +
COLUMN_VALUE + " integer)");
}
private void dropTable() {
jdbcTemplate.execute("DROP TABLE IF EXISTS " + TABLE_NAME);
}
class Player {
String name;
int value;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Player)) return false;
Player player = (Player) o;
return new EqualsBuilder().append(value, player.value).append(name, player.name).isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37).append(name).append(value).toHashCode();
}
}
@Nested
class JdbcTemplateTest_ExecuteTestCase {
@BeforeEach
void before() {
jdbcTemplate = new JdbcTemplate(dataSource);
dropTable();
}
@AfterEach
void after() {
dropTable();
}
@Test
void test() throws ClassNotFoundException, IllegalAccessException, InstantiationException, SQLException {
createTable();
//region Check result
Class.forName("org.h2.Driver").newInstance();
Connection connection = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD);
ResultSet resultSet = connection.getMetaData().getTables(JDBC_DB_NAME.toUpperCase(), "PUBLIC",
TABLE_NAME.toUpperCase(), new String[]{"TABLE"});
assertTrue(resultSet.next());
resultSet.close();
connection.close();
//endregion
}
}
}