diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..32b1afc --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,136 @@ +name: Build + +on: + push: + branches: + - master + - main + workflow_run: + workflows: [ "Patch Version" ] + types: + - completed + + +jobs: + increment-version: + runs-on: ubuntu-latest + timeout-minutes: 60 + permissions: + contents: write + steps: + + # ==================== SETUPS ==================== + + - name: Checkout Codebase + uses: actions/checkout@v4 + with: + node-version: '12.x' + + - name: Setup Python + uses: actions/setup-python@v5.2.0 + + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: zulu + java-version: 21 + + # ==================== INCREMENT VERSION ===================== + + - name: Increment Version + run: | + python3 scripts/increment_version.py + + # ==================== PUBLISH ==================== + + - name: Commit to Repository + uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: Incremented patch version + commit_options: '--no-verify --signoff' + commit_user_name: Voinea Radu + commit_user_email: contact@voinearadu.com + + build: + needs: increment-version + runs-on: ubuntu-latest + permissions: + contents: write + steps: + + # ==================== SETUPS ==================== + + - name: Checkout Codebase + uses: actions/checkout@v4 + with: + node-version: '12.x' + + - name: Setup Python + uses: actions/setup-python@v5.2.0 + + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: zulu + java-version: 21 + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + with: + build-scan-publish: true + build-scan-terms-of-use-url: "https://gradle.com/terms-of-service" + build-scan-terms-of-use-agree: "yes" + + # ==================== BUILD ==================== + + - name: Export Credentials + run: | + mkdir -p ~/.gradle + echo " + voinearadu.url=https://repository.voinearadu.com/repository/maven-releases/ + voinearadu.auth.username=admin + voinearaduauth.password=${{ secrets.NEXUS_PASSWORD }} + " > ~/.gradle/gradle.properties + + - name: Cache Gradle Dependencies + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle + restore-keys: | + ${{ runner.os }}-gradle + + - name: Set gradle permissions + run: chmod +x gradlew + + - name: Execute Gradle Build + run: ./gradlew build + + # ==================== ENVIRONMENT VARIABLES ==================== + + - name: External scripts - Environment Variables + run: | + echo "VERSION=$(./gradlew properties -q | grep "^version:" | awk '{print $2}')" >> $GITHUB_ENV + echo "ARTIFACT=$(./gradlew properties -q | grep "^name:" | awk '{print $2}')" >> $GITHUB_ENV + echo "GROUP=$(./gradlew properties -q | grep "^group:" | awk '{print $2}')" >> $GITHUB_ENV + echo "REPOSITORY_NAME=$(echo $REPOSITORY_NAME | awk -F'/' '{print $2}')" >> $GITHUB_ENV + + # ==================== PUBLISH ==================== + + - name: Execute Gradle Publish + run: ./gradlew publish + + - name: Automatic Releases + uses: marvinpinto/action-automatic-releases@v1.2.1 + with: + repo_token: "${{ secrets.GITHUB_TOKEN }}" + automatic_release_tag: "${{ env.VERSION }}" + prerelease: ${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/master' }} + title: "${{ env.VERSION }}" + files: | + build/libs/*.jar + src/*/build/libs/*.jar + src/*/*/build/libs/*.jar + src/*/*/*/build/libs/*.jar diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b8f2d7b --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +### IntelliJ IDEA ### +.idea/ +*.iml + +### Gradle ### +.gradle/ +build/ + +### Groovy ### +lib + +### Python ### +venv + +### Temporary Files ### +tmp/ +tmp-* +py.log +*.log +test_workflow.sh +__pycache__/ +scripts/libs/__pycache__/ +scripts/libs/commands/__pycache__/ + +### Build Files ### + +### Credentrials Files ### +scripts/credentials.py +/config/ +http.key +id.rsa +digital_ocean.token \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5f968a4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Voinea Radu-Mihai + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2708149 --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# Logger + +## How to add to your project + +```kotlin +repositories { + maven("https://repository.voinearadu.com/repository/maven-releases/") + maven("https://repo.voinearadu.com/") // The short version of the above (might be slower on high latency connections) +} + +dependencies { + implementation("com.voinearadu:utils:VERSION") + + // To use the redis_manager + implementation("redis.clients:jedis:") +} +``` \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..d8cfb92 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,73 @@ +plugins { + id("java") + id("java-library") + id("maven-publish") +} + +val _group = libs.versions.group.get() +val _version = libs.versions.version.get() + +group = _group +version = _version + +repositories { + mavenCentral() + maven("https://repository.voinearadu.dev/repository/maven-releases/") +} + +dependencies { + // Dependencies + api(libs.gson) + api(libs.slf4j) + api(libs.apache.commons.compress) + api(libs.apache.commons.lang3) + api(libs.apache.commons.pool2) + api(libs.apache.commons.io) + compileOnly(libs.jedis) + testImplementation(libs.jedis) + + // Annotations + compileOnly("org.projectlombok:lombok:1.18.34") + annotationProcessor("org.projectlombok:lombok:1.18.34") + testCompileOnly("org.projectlombok:lombok:1.18.34") + testAnnotationProcessor("org.projectlombok:lombok:1.18.34") + + compileOnly("org.jetbrains:annotations:24.1.0") + annotationProcessor("org.jetbrains:annotations:24.1.0") + testCompileOnly("org.jetbrains:annotations:24.1.0") + testAnnotationProcessor("org.jetbrains:annotations:24.1.0") + + // Tests + testImplementation(platform(libs.junit.bom)) + testImplementation(libs.junit.jupiter) +} + +tasks { + java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 + } + + publishing { + publications { + create("maven") + } + + repositories { + maven(url = (project.findProperty("voinearadu.url") ?: "") as String) { + credentials(PasswordCredentials::class) { + username = (project.findProperty("voinearadu.auth.username") ?: "") as String + password = (project.findProperty("voinearadu.auth.password") ?: "") as String + } + } + } + } + + test { + useJUnitPlatform() + testLogging { + events("passed", "skipped", "failed") + } + } +} + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..6eeb0fa --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,26 @@ +[versions] +version = "1.0.0" +group = "com.voinearadu" + +[plugins] +shadow = { id = "com.github.johnrengelman.shadow", version = "8.1.1" } + +[libraries] +# Testing +junit_jupiter = { module = "org.junit.jupiter:junit-jupiter ", version = "5.11.0" } +junit_bom = { module = "org.junit:junit-bom", version = "5.11.0" } + +# Annotations +lombok = { module = "org.projectlombok:lombok", version = "1.18.34" } +jetbrains_annotations = { module = "org.jetbrains:annotations", version = "24.1.0" } + +# Dependencies +gson = { module = "com.google.code.gson:gson", version = "2.11.0" } +jedis = { module = "redis.clients:jedis", version = "5.1.5" } +slf4j = { module = "org.slf4j:slf4j-api", version = "2.0.16" } +apache_commons_compress = { module = "org.apache.commons:commons-compress", version = "1.27.1" } +apache_commons_lang3 = { module = "org.apache.commons:commons-lang3", version = "3.16.0" } +apache_commons_pool2 = { module = "org.apache.commons:commons-pool2", version = "2.12.0" } +apache_commons_io = { module = "commons-io:commons-io", version = "2.16.1" } + + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..249e583 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..181d377 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Thu Sep 05 05:14:33 EEST 2024 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..1b6c787 --- /dev/null +++ b/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/scripts/increment_version.py b/scripts/increment_version.py new file mode 100644 index 0000000..9614d06 --- /dev/null +++ b/scripts/increment_version.py @@ -0,0 +1,63 @@ +# Requirements +# - Have at least one of the files `gradle/libs.versions.toml` `build.gradle.kts` `build.gradle` +# - Have at least one of them have either `project = "some_version"` or `version = "some_version"` + +import os + + +def increment_version(current_version: str): + version_codes = current_version.split(".") + version_codes[-1] = str(int(version_codes[-1]) + 1) + new_version = ".".join(version_codes) + return new_version + + +def process_version_line(line: str): + simple_line = line.strip().replace(" ", "") + + replace_str: str = "" + + if simple_line.startswith("project="): + replace_str = "project=" + if simple_line.startswith("version="): + replace_str = "version=" + + current_version = simple_line.split(replace_str)[1].replace("\"", "") + + for char in current_version: + if char.isdigit() or char == ".": + new_version = increment_version(current_version) + return line.replace(current_version, new_version) + else: + return line + + +def main(): + version_containers_files = ["gradle/libs.versions.toml", "build.gradle.kts", "build.gradle"] + + for file_path in version_containers_files: + if not os.path.exists(file_path): + continue + + new_file_contents = "" + + with open(file_path, "r") as file: + file_content = file.read() + + for line in file_content.splitlines(): + simple_line = line.strip().replace(" ", "") + + if simple_line.startswith("version=") or simple_line.startswith("project="): + try: + new_file_contents += f"{process_version_line(line)}\n" + except: + new_file_contents += f"{line}\n" + else: + new_file_contents += f"{line}\n" + + with open(file_path, "w") as file: + file.write(new_file_contents) + + +if __name__ == "__main__": + main() diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..1fa864d --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "utils" \ No newline at end of file diff --git a/src/main/java/com.voinearadu/event_manager/EventManager.java b/src/main/java/com.voinearadu/event_manager/EventManager.java new file mode 100644 index 0000000..9124988 --- /dev/null +++ b/src/main/java/com.voinearadu/event_manager/EventManager.java @@ -0,0 +1,191 @@ +package com.voinearadu.event_manager; + +import com.voinearadu.event_manager.annotation.EventHandler; +import com.voinearadu.event_manager.dto.IEvent; +import com.voinearadu.logger.Logger; +import lombok.NoArgsConstructor; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +@NoArgsConstructor +public class EventManager { + + private final HashMap, Object> objects = new HashMap<>(); + private final HashMap, List> methods = new HashMap<>(); + private ExternalRegistrar externalRegister = (object, method, eventClass) -> false; + private ExternalUnregister externalUnregister = (method, eventClass) -> false; + + public interface ExternalRegistrar { + boolean execute(Object object, Method method, Class eventClass); + } + + public interface ExternalUnregister { + boolean execute(Method method, Class eventClass); + } + + @SuppressWarnings("unused") + public void registerExternalRegistrar(ExternalRegistrar register, ExternalUnregister unregister) { + externalRegister = register; + externalUnregister = unregister; + } + + public void register(Class clazz) { + try { + register(clazz.getDeclaredConstructor().newInstance()); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | + NoSuchMethodException error) { + Logger.error(error); + } + } + + public void register(Object object) { + for (Method method : object.getClass().getDeclaredMethods()) { + register(object, method); + } + + sortMethods(); + } + + @SuppressWarnings("unused") + public void unregister(Object object) { + for (Method method : object.getClass().getMethods()) { + unregister(method); + } + } + + public void fire(Object event) { + Class eventClass = event.getClass(); + + if (!methods.containsKey(eventClass)) { + return; + } + + List eventMethods = methods.get(eventClass); + + for (EventMethod method : eventMethods) { + method.fire(event); + } + } + + protected Class getEventClass(Method method) { + if (!method.isAnnotationPresent(EventHandler.class)) { + return null; + } + + EventHandler annotation = method.getAnnotation(EventHandler.class); + + if (annotation.ignore()) { + return null; + } + + if (method.getParameterCount() != 1) { + Logger.error("Method " + method.getName() + " from class " + method.getDeclaringClass() + " has " + method.getParameterCount() + " parameters, expected 1"); + return null; + } + + return method.getParameterTypes()[0]; + } + + private void register(Object object, Method method) { + Class eventClass = getEventClass(method); + + if (eventClass == null) { + return; + } + + if (!IEvent.class.isAssignableFrom(eventClass)) { + boolean result = externalRegister.execute(object, method, eventClass); + + if (!result) { + Logger.error("Failed to register method " + method.getName() + " from class " + method.getDeclaringClass() + " with event class " + eventClass.getName()); + } + + return; + } + + Class parentClass = object.getClass(); + + if (!objects.containsKey(parentClass)) { + objects.put(parentClass, object); + } + + List eventMethods = methods.getOrDefault(eventClass, new ArrayList<>()); + eventMethods.add(new EventMethod(method)); + methods.put(eventClass, eventMethods); + } + + private void unregister(Method method) { + Class eventClass = getEventClass(method); + + if (eventClass == null) { + return; + } + + if (!IEvent.class.isAssignableFrom(eventClass)) { + boolean result = externalUnregister.execute(method, eventClass); + + if (!result) { + Logger.error("Failed to register method " + method.getName() + " from class " + method.getDeclaringClass() + " with event class " + eventClass.getName()); + } + + return; + } + + List eventMethods = methods.getOrDefault(eventClass, new ArrayList<>()); + eventMethods.remove(new EventMethod(method)); + methods.put(eventClass, eventMethods); + } + + private void sortMethods() { + for (Class eventClass : methods.keySet()) { + List eventMethods = methods.getOrDefault(eventClass, new ArrayList<>()); + eventMethods.sort(new EventMethod.Comparator()); + methods.put(eventClass, eventMethods); + } + } + + public class EventMethod { + private final Method method; + private final EventHandler annotation; + + public EventMethod(Method method) { + this.method = method; + this.annotation = method.getAnnotation(EventHandler.class); + } + + public void fire(Object event) { + try { + method.setAccessible(true); + Object object = objects.get(method.getDeclaringClass()); + method.invoke(object, event); + } catch (IllegalAccessException | InvocationTargetException error) { + Logger.error(error); + } + } + + public static class Comparator implements java.util.Comparator { + @Override + public int compare(EventMethod object1, EventMethod object2) { + return object1.annotation.order() - object2.annotation.order(); + } + } + + @Override + public boolean equals(Object object) { + if (!(object instanceof EventMethod eventMethod)) { + return false; + } + + return eventMethod.method.equals(method); + } + + @Override + public int hashCode() { + return method.hashCode(); + } + } +} diff --git a/src/main/java/com.voinearadu/event_manager/annotation/EventHandler.java b/src/main/java/com.voinearadu/event_manager/annotation/EventHandler.java new file mode 100644 index 0000000..0c2c358 --- /dev/null +++ b/src/main/java/com.voinearadu/event_manager/annotation/EventHandler.java @@ -0,0 +1,16 @@ +package com.voinearadu.event_manager.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface EventHandler { + + int order() default 0; + + boolean ignore() default false; + +} diff --git a/src/main/java/com.voinearadu/event_manager/dto/IEvent.java b/src/main/java/com.voinearadu/event_manager/dto/IEvent.java new file mode 100644 index 0000000..e74fed1 --- /dev/null +++ b/src/main/java/com.voinearadu/event_manager/dto/IEvent.java @@ -0,0 +1,4 @@ +package com.voinearadu.event_manager.dto; + +public interface IEvent { +} diff --git a/src/main/java/com.voinearadu/file_manager/dto/GsonSerializer.java b/src/main/java/com.voinearadu/file_manager/dto/GsonSerializer.java new file mode 100644 index 0000000..9ec4c3b --- /dev/null +++ b/src/main/java/com.voinearadu/file_manager/dto/GsonSerializer.java @@ -0,0 +1,16 @@ +package com.voinearadu.file_manager.dto; + +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonSerializer; +import lombok.Getter; + +public abstract class GsonSerializer implements JsonSerializer, JsonDeserializer { + + protected final ClassLoader classLoader; + private final @Getter Class serializedClass; + + protected GsonSerializer(ClassLoader classLoader, Class serializedClass) { + this.serializedClass = serializedClass; + this.classLoader = classLoader; + } +} diff --git a/src/main/java/com.voinearadu/file_manager/dto/gson/InterfaceGsonTypeAdapter.java b/src/main/java/com.voinearadu/file_manager/dto/gson/InterfaceGsonTypeAdapter.java new file mode 100644 index 0000000..b2fd1db --- /dev/null +++ b/src/main/java/com.voinearadu/file_manager/dto/gson/InterfaceGsonTypeAdapter.java @@ -0,0 +1,88 @@ +package com.voinearadu.file_manager.dto.gson; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.voinearadu.file_manager.dto.serializable.ISerializable; +import com.voinearadu.logger.Logger; +import com.voinearadu.reflections.Reflections; +import lombok.Getter; +import lombok.SneakyThrows; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + + +@SuppressWarnings("unchecked") +public class InterfaceGsonTypeAdapter implements JsonDeserializer { + + public static final String CLASS_NAME = "class_name"; + + protected final ClassLoader classLoader; + private final @Getter Class serializedClass; + + public InterfaceGsonTypeAdapter(ClassLoader classLoader, Class serializedClass) { + this.serializedClass = serializedClass; + this.classLoader = classLoader; + } + + @SuppressWarnings("unchecked") + public static List> generate(ClassLoader classLoader, Class... classes) { + List> output = new ArrayList<>(); + + Arrays.stream(classes) + .filter(Class::isInterface) + .forEach(clazz -> output.add(new InterfaceGsonTypeAdapter<>(classLoader, clazz))); + + return output; + } + + + @SuppressWarnings({"unchecked", "unused"}) + public static List> generate(ClassLoader classLoader, Reflections reflections) { + Set> classes = reflections.getOfType(ISerializable.class); + + classes.stream() + .filter(clazz -> !clazz.isInterface()) + .forEach(clazz -> { + boolean flag = Reflections.getFields(clazz).stream() + .anyMatch(field -> field.getName().equals(CLASS_NAME) && field.getType().equals(String.class)); + + if (!flag) { + Logger.error("Class " + clazz.getName() + " implements ISerializable but does not have a field `class_name`. This can cause issues with deserialization of this class."); + } + }); + + + return generate(classLoader, classes.toArray(new Class[0])); + } + + @SneakyThrows(value = {ClassNotFoundException.class}) + @Override + public T deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException { + ISerializable output; + + JsonElement classElement = json.getAsJsonObject().get(CLASS_NAME); + + if (classElement == null) { + Logger.error("Field class_name was not not found in json. Please check the definition of " + type.getTypeName() + " and make sure it has the field class_name."); + Logger.error("Json:"); + Logger.error(json.toString()); + return null; + } + + String className = json.getAsJsonObject().get(CLASS_NAME).getAsString(); + + //noinspection unchecked + Class clazz = (Class) classLoader.loadClass(className); + + output = context.deserialize(json, clazz); + + return (T) output; + } + +} diff --git a/src/main/java/com.voinearadu/file_manager/dto/gson/SerializableListGsonTypeAdapter.java b/src/main/java/com.voinearadu/file_manager/dto/gson/SerializableListGsonTypeAdapter.java new file mode 100644 index 0000000..2eaec3e --- /dev/null +++ b/src/main/java/com.voinearadu/file_manager/dto/gson/SerializableListGsonTypeAdapter.java @@ -0,0 +1,56 @@ +package com.voinearadu.file_manager.dto.gson; + +import com.google.gson.*; +import com.voinearadu.file_manager.dto.GsonSerializer; +import com.voinearadu.file_manager.dto.serializable.SerializableList; +import com.voinearadu.logger.Logger; + +import java.lang.reflect.Type; +import java.util.ArrayList; + + +@SuppressWarnings("rawtypes") +public class SerializableListGsonTypeAdapter extends GsonSerializer { + + private static final String CLASS_NAME = "class_name"; + private static final String VALUES = "values"; + + public SerializableListGsonTypeAdapter(ClassLoader classLoader) { + super(classLoader, SerializableList.class); + } + + @SuppressWarnings("unchecked") + @Override + public SerializableList deserialize(JsonElement json, Type type, JsonDeserializationContext context) { + String className = json.getAsJsonObject().get(CLASS_NAME).getAsString(); + + try { + Class clazz = classLoader.loadClass(className); + + JsonArray data = json.getAsJsonObject().get(VALUES).getAsJsonArray(); + java.util.List output = new ArrayList(); + data.forEach((element) -> output.add(context.deserialize(element, clazz))); + + return new SerializableList(clazz, output); + } catch (ClassNotFoundException error) { + Logger.error(error); + return null; //NOPMD - suppressed ReturnEmptyCollectionRatherThanNull - If the class of the list can not be + // found, no list can be created + } + } + + @SuppressWarnings("unchecked") + @Override + public JsonElement serialize(SerializableList list, Type type, JsonSerializationContext context) { + JsonObject object = new JsonObject(); + + object.addProperty(CLASS_NAME, list.getValueClass().getName()); + + JsonArray array = new JsonArray(); + list.forEach((element) -> array.add(context.serialize(element))); + + object.add(VALUES, array); + + return object; + } +} diff --git a/src/main/java/com.voinearadu/file_manager/dto/gson/SerializableMapGsonTypeAdapter.java b/src/main/java/com.voinearadu/file_manager/dto/gson/SerializableMapGsonTypeAdapter.java new file mode 100644 index 0000000..b6e976d --- /dev/null +++ b/src/main/java/com.voinearadu/file_manager/dto/gson/SerializableMapGsonTypeAdapter.java @@ -0,0 +1,70 @@ +package com.voinearadu.file_manager.dto.gson; + +import com.google.gson.*; +import com.voinearadu.file_manager.dto.GsonSerializer; +import com.voinearadu.file_manager.dto.serializable.SerializableMap; +import com.voinearadu.logger.Logger; + +import java.lang.reflect.Type; +import java.util.HashMap; + + +@SuppressWarnings({"rawtypes", "unchecked"}) +public class SerializableMapGsonTypeAdapter extends GsonSerializer { + + private static final String KEY_CLASS_NAME = "key_class_name"; + private static final String VALUE_CLASS_NAME = "value_class_name"; + private static final String KEYS = "keys"; + private static final String VALUES = "values"; + + public SerializableMapGsonTypeAdapter(ClassLoader classLoader) { + super(classLoader, SerializableMap.class); + } + + @Override + public SerializableMap deserialize(JsonElement json, Type type, JsonDeserializationContext context) { + String keyClassName = json.getAsJsonObject().get(KEY_CLASS_NAME).getAsString(); + String valueClassName = json.getAsJsonObject().get(VALUE_CLASS_NAME).getAsString(); + + try { + Class keyClass = classLoader.loadClass(keyClassName); + Class valueClass = classLoader.loadClass(valueClassName); + + JsonArray keys = json.getAsJsonObject().get(KEYS).getAsJsonArray(); + JsonArray values = json.getAsJsonObject().get(VALUES).getAsJsonArray(); + + HashMap output = new HashMap<>(); + + for (int i = 0; i < keys.size(); i++) { + output.put(context.deserialize(keys.get(i), keyClass), context.deserialize(values.get(i), valueClass)); + } + + return new SerializableMap(keyClass, valueClass, output); + + } catch (ClassNotFoundException error) { + Logger.error(error); + return null; + } + } + + @Override + public JsonElement serialize(SerializableMap list, Type type, JsonSerializationContext context) { + JsonObject object = new JsonObject(); + + object.addProperty(KEY_CLASS_NAME, list.getKeyClass().getName()); + object.addProperty(VALUE_CLASS_NAME, list.getValueClass().getName()); + + JsonArray keyArray = new JsonArray(); + JsonArray valueArray = new JsonArray(); + + list.forEach((key, value) -> { + keyArray.add(context.serialize(key)); + valueArray.add(context.serialize(value)); + }); + + object.add(KEYS, keyArray); + object.add(VALUES, valueArray); + + return object; + } +} diff --git a/src/main/java/com.voinearadu/file_manager/dto/gson/SerializableObjectTypeAdapter.java b/src/main/java/com.voinearadu/file_manager/dto/gson/SerializableObjectTypeAdapter.java new file mode 100644 index 0000000..150bd0c --- /dev/null +++ b/src/main/java/com.voinearadu/file_manager/dto/gson/SerializableObjectTypeAdapter.java @@ -0,0 +1,51 @@ +package com.voinearadu.file_manager.dto.gson; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonSerializationContext; +import com.voinearadu.file_manager.dto.GsonSerializer; +import com.voinearadu.file_manager.dto.serializable.SerializableObject; +import com.voinearadu.logger.Logger; + +import java.lang.reflect.Type; + + +@SuppressWarnings("rawtypes") +public class SerializableObjectTypeAdapter extends GsonSerializer { + + private static final String CLASS_NAME = "class_name"; + private static final String DATA = "data"; + + public SerializableObjectTypeAdapter(ClassLoader classLoader) { + super(classLoader, SerializableObject.class); + } + + @Override + public SerializableObject deserialize(JsonElement json, Type type, JsonDeserializationContext context) { + String className = json.getAsJsonObject().get(CLASS_NAME).getAsString(); + + try { + Class clazz = classLoader.loadClass(className); + + JsonElement jsonData = json.getAsJsonObject().get(DATA); + Object object = context.deserialize(jsonData, clazz); + + //noinspection unchecked + return new SerializableObject(clazz, object); + } catch (ClassNotFoundException error) { + Logger.error(error); + return null; + } + } + + @Override + public JsonElement serialize(SerializableObject object, Type type, JsonSerializationContext context) { + JsonObject jsonObject = new JsonObject(); + + jsonObject.addProperty(CLASS_NAME, object.objectClass().getName()); + jsonObject.add(DATA, context.serialize(object.object())); + + return jsonObject; + } +} diff --git a/src/main/java/com.voinearadu/file_manager/dto/serializable/ISerializable.java b/src/main/java/com.voinearadu/file_manager/dto/serializable/ISerializable.java new file mode 100644 index 0000000..15529d2 --- /dev/null +++ b/src/main/java/com.voinearadu/file_manager/dto/serializable/ISerializable.java @@ -0,0 +1,4 @@ +package com.voinearadu.file_manager.dto.serializable; + +public interface ISerializable { +} diff --git a/src/main/java/com.voinearadu/file_manager/dto/serializable/SerializableList.java b/src/main/java/com.voinearadu/file_manager/dto/serializable/SerializableList.java new file mode 100644 index 0000000..a720a3b --- /dev/null +++ b/src/main/java/com.voinearadu/file_manager/dto/serializable/SerializableList.java @@ -0,0 +1,22 @@ +package com.voinearadu.file_manager.dto.serializable; + +import lombok.Getter; + +import java.util.ArrayList; +import java.util.Collection; + +@Getter +public class SerializableList extends ArrayList { + + private final Class valueClass; + + public SerializableList(Class valueClass, Collection list) { + super(list); + this.valueClass = valueClass; + } + + @SuppressWarnings("unused") + public SerializableList(Class clazz) { + this(clazz, new ArrayList<>()); + } +} diff --git a/src/main/java/com.voinearadu/file_manager/dto/serializable/SerializableMap.java b/src/main/java/com.voinearadu/file_manager/dto/serializable/SerializableMap.java new file mode 100644 index 0000000..027953d --- /dev/null +++ b/src/main/java/com.voinearadu/file_manager/dto/serializable/SerializableMap.java @@ -0,0 +1,25 @@ +package com.voinearadu.file_manager.dto.serializable; + +import lombok.Getter; + +import java.util.HashMap; +import java.util.Map; + +@Getter +public class SerializableMap extends HashMap { + + private final Class keyClass; + private final Class valueClass; + + public SerializableMap(Class keyClass, Class valueClass, Map map) { + this.keyClass = keyClass; + this.valueClass = valueClass; + putAll(map); + } + + @SuppressWarnings("unused") + public SerializableMap(Class keyClass, Class valueClass) { + this(keyClass, valueClass, new HashMap<>()); + } + +} diff --git a/src/main/java/com.voinearadu/file_manager/dto/serializable/SerializableObject.java b/src/main/java/com.voinearadu/file_manager/dto/serializable/SerializableObject.java new file mode 100644 index 0000000..a63d79e --- /dev/null +++ b/src/main/java/com.voinearadu/file_manager/dto/serializable/SerializableObject.java @@ -0,0 +1,5 @@ +package com.voinearadu.file_manager.dto.serializable; + +public record SerializableObject(Class objectClass, Object object) { + +} diff --git a/src/main/java/com.voinearadu/file_manager/manager/FileManager.java b/src/main/java/com.voinearadu/file_manager/manager/FileManager.java new file mode 100644 index 0000000..a098dbf --- /dev/null +++ b/src/main/java/com.voinearadu/file_manager/manager/FileManager.java @@ -0,0 +1,169 @@ +package com.voinearadu.file_manager.manager; + +import com.google.gson.Gson; +import com.voinearadu.file_manager.utils.DateUtils; +import com.voinearadu.file_manager.utils.PathUtils; +import com.voinearadu.lambda.lambda.ReturnLambdaExecutor; +import com.voinearadu.logger.Logger; +import lombok.SneakyThrows; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public record FileManager(@NotNull ReturnLambdaExecutor gsonProvider, @NotNull String basePath) { + + @SuppressWarnings("ResultOfMethodCallIgnored") + public synchronized String readFile(@NotNull String directory, @NotNull String fileName) { + Path path = Paths.get(getDataFolder().getPath(), directory, fileName); + StringBuilder json = new StringBuilder(); + File file = path.toFile(); + + if (!file.exists()) { + try { + file.createNewFile(); + } catch (IOException error) { + Logger.error(error); + Logger.error("Could not create file " + fileName + " in directory " + directory); + return ""; + } + } + + try (BufferedReader reader = Files.newBufferedReader(path)) { + String curLine = reader.readLine(); + while (curLine != null) { + json.append(curLine).append('\n'); + curLine = reader.readLine(); + } + + return json.toString().strip(); + } catch (Exception error) { + Logger.error(error); + Logger.warn("Could not read file " + fileName + " in directory " + directory); + return ""; + } + } + + @SuppressWarnings({"ResultOfMethodCallIgnored", "UnusedReturnValue"}) + public synchronized @Nullable Path writeFile(@NotNull String directory, @NotNull String fileName, @NotNull String content) { + Path path = Paths.get(getDataFolder().getPath(), directory, fileName); + File file = path.toFile(); + file.getParentFile().mkdirs(); + + if (!file.exists()) { + try { + file.createNewFile(); + } catch (IOException error) { + Logger.error(error); + Logger.error("Could not create file " + fileName + " in directory " + directory); + return null; + } + } + + try (BufferedWriter writer = Files.newBufferedWriter(file.toPath())) { + writer.write(content); + } catch (Exception error) { + Logger.error(error); + Logger.error("Could not write to file " + fileName + " in directory " + directory); + } + + return path; + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + public synchronized void writeFileAndBackup(@NotNull String directory, @NotNull String fileName, @NotNull String newContent) { + Path path = Paths.get(getDataFolder().getPath(), directory, fileName); + File file = path.toFile(); + file.getParentFile().mkdirs(); + + String oldContent = readFile(directory, fileName); + + if (!oldContent.equals(newContent)) { + if (oldContent.isEmpty()) { + Logger.warn("The file " + path + " was empty. Skipping backup..."); + } else { + Logger.warn("The file " + path + " has been automatically modified. Creating a backup..."); + Logger.log("================================================"); + Logger.log("Old content: " + oldContent); + Logger.log("New content: " + newContent); + Logger.log("================================================"); + + String date = DateUtils.getDate("dd_MM_yyyy_HH_mm_ss"); + String backupFileName = fileName.split(".json")[0] + "_backup_" + date + ".json"; + + writeFile(directory, backupFileName, oldContent); + } + + writeFile(directory, fileName, newContent); + } + } + + public synchronized void save(Object object) { + save(object, ""); + } + + public synchronized void save(Object object, String directory) { + Class clazz = object.getClass(); + + save(object, directory, PathUtils.toSnakeCase(clazz.getSimpleName())); + } + + @SneakyThrows + public synchronized void save(@NotNull Object object, @NotNull String directory, @NotNull String fileName) { + String json = gsonProvider.execute().toJson(object); + + if (!fileName.endsWith(".json")) { + fileName += ".json"; + } + + writeFileAndBackup(directory, fileName, json); + } + + public @NotNull T load(@NotNull Class clazz) { + return load(clazz, ""); + } + + public @NotNull T load(@NotNull Class clazz, @NotNull String directory) { + return load(clazz, directory, PathUtils.toSnakeCase(clazz.getSimpleName())); + } + + @SneakyThrows + public synchronized @NotNull T load(@NotNull Class clazz, @NotNull String directory, @NotNull String fileName) { + if (!fileName.endsWith(".json")) { + fileName += ".json"; + } + + String oldJson = readFile(directory, fileName); + + T output; + + if (oldJson.isEmpty()) { + Logger.log("The file " + fileName + " in directory '" + directory + "' is empty. Creating a new instance..."); + output = clazz.getDeclaredConstructor().newInstance(); + } else { + output = gsonProvider.execute().fromJson(oldJson, clazz); + } + + String newJson = gsonProvider.execute().toJson(output); + + writeFileAndBackup(directory, fileName, newJson); + + return output; + } + + public @NotNull File getDataFolder() { + String path = this.basePath; + if (!path.startsWith("/")) { + path = "/" + path; + } + + return new File(System.getProperty("user.dir") + path); + } + +} diff --git a/src/main/java/com.voinearadu/file_manager/utils/DateUtils.java b/src/main/java/com.voinearadu/file_manager/utils/DateUtils.java new file mode 100644 index 0000000..31b1196 --- /dev/null +++ b/src/main/java/com.voinearadu/file_manager/utils/DateUtils.java @@ -0,0 +1,91 @@ +package com.voinearadu.file_manager.utils; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; + +public class DateUtils { + + public static String getDate(String format) { + return new SimpleDateFormat(format).format(Calendar.getInstance().getTime()); + } + + @SuppressWarnings("unused") + public static String getDateAndTime() { + return getDate("HH:mm:ss dd-MM-yyyy"); + } + + @SuppressWarnings("unused") + public static String getDateOnly() { + return getDate("dd-MM-yyyy"); + } + + @SuppressWarnings("unused") + public static String getTimeOnly() { + return getDate("HH:mm:ss"); + } + + @SuppressWarnings("unused") + public static String convertUnixTimeToDate(long unixTimestamp) { + Date date = new Date(unixTimestamp); + + SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss z"); + sdf.setTimeZone(TimeZone.getTimeZone("UTC")); + + return sdf.format(date); + } + + @SuppressWarnings("unused") + public static String convertToPeriod(long milliseconds) { + if (milliseconds < 0) { + return "0s"; + } + + long seconds = milliseconds / 1000; + long minutes = seconds / 60; + long hours = minutes / 60; + long days = hours / 24; + + milliseconds = milliseconds % 60; + seconds = seconds % 60; + minutes = minutes % 60; + hours = hours % 24; + + String output = ""; + + if (days > 0) { + output += days + "d "; + output += hours + "h "; + output += minutes + "m "; + output += seconds + "s "; + + return output; + } + + if (hours > 0) { + output += hours + "h "; + output += minutes + "m "; + output += seconds + "s "; + + return output; + } + + if (minutes > 0) { + output += minutes + "m "; + output += seconds + "s "; + + return output; + } + + if (seconds > 0) { + output += seconds + "s "; + + return output; + } + + return milliseconds + "ms "; + } + + +} diff --git a/src/main/java/com.voinearadu/file_manager/utils/PathUtils.java b/src/main/java/com.voinearadu/file_manager/utils/PathUtils.java new file mode 100644 index 0000000..c825a23 --- /dev/null +++ b/src/main/java/com.voinearadu/file_manager/utils/PathUtils.java @@ -0,0 +1,38 @@ +package com.voinearadu.file_manager.utils; + +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.regex.Pattern; + +public class PathUtils { + + private final static Pattern snakeCasePattern = Pattern.compile("([A-Z])"); + + public static String join(String... paths) { + StringBuilder builder = new StringBuilder(); + + for (String path : paths) { + builder.append(path); + builder.append('/'); + } + + return builder.toString(); + } + + @SuppressWarnings("unused") + public static String join(List paths) { + return join(paths.toArray(new String[0])); + } + + public static @NotNull String toSnakeCase(@NotNull String string) { + String output = snakeCasePattern.matcher(string).replaceAll("_$1").toLowerCase(); + + if (output.startsWith("_")) { + output = output.substring(1); + } + + return output; + } + +} diff --git a/src/main/java/com.voinearadu/lambda/CancelableTimeTask.java b/src/main/java/com.voinearadu/lambda/CancelableTimeTask.java new file mode 100644 index 0000000..7395ca0 --- /dev/null +++ b/src/main/java/com.voinearadu/lambda/CancelableTimeTask.java @@ -0,0 +1,39 @@ +package com.voinearadu.lambda; + +import lombok.Getter; +import lombok.Setter; +import org.jetbrains.annotations.Nullable; + +import java.util.TimerTask; + +@Getter +public abstract class CancelableTimeTask extends TimerTask { + + @Setter + private @Nullable Thread thread; + private boolean canceled; + + public abstract void execute(); + + @Override + public void run() { + if (canceled) { + if (thread != null) { + thread.interrupt(); + } + return; + } + execute(); + } + + @Override + public boolean cancel() { + this.canceled = true; + boolean result = super.cancel(); + if (thread != null) { + thread.interrupt(); + } + + return result; + } +} diff --git a/src/main/java/com.voinearadu/lambda/ScheduleUtils.java b/src/main/java/com.voinearadu/lambda/ScheduleUtils.java new file mode 100644 index 0000000..5b28a08 --- /dev/null +++ b/src/main/java/com.voinearadu/lambda/ScheduleUtils.java @@ -0,0 +1,85 @@ +package com.voinearadu.lambda; + +import com.voinearadu.lambda.lambda.LambdaExecutor; +import lombok.Getter; +import org.jetbrains.annotations.NotNull; + +import java.util.Timer; + +@SuppressWarnings({"UnusedReturnValue", "unused"}) +@Getter +public class ScheduleUtils { + + public static CancelableTimeTask runTaskLater(@NotNull LambdaExecutor executor, long delay) { + CancelableTimeTask task = new CancelableTimeTask() { + @Override + public void execute() { + executor.execute(); + } + }; + + return runTaskLater(task, delay); + } + + public static CancelableTimeTask runTaskLater(@NotNull CancelableTimeTask task, long delay) { + Timer timer = new Timer(); + timer.schedule(task, delay); + + return task; + } + + public static @NotNull CancelableTimeTask runTaskTimer(@NotNull LambdaExecutor executor, long period) { + return runTaskTimer(new CancelableTimeTask() { + @Override + public void execute() { + executor.execute(); + } + }, period); + } + + public static @NotNull CancelableTimeTask runTaskTimer(@NotNull CancelableTimeTask task, long period) { + Timer timer = new Timer(); + timer.schedule(task, 0, period); + + return task; + } + + public static @NotNull CancelableTimeTask runTaskLaterAsync(@NotNull LambdaExecutor executor, long delay) { + CancelableTimeTask task = new CancelableTimeTask() { + @Override + public void execute() { + executor.execute(); + } + }; + + Thread thread = Thread.ofVirtual().start(() -> runTaskLater(task, delay)); + task.setThread(thread); + return task; + } + + public static @NotNull CancelableTimeTask runTaskTimerAsync(@NotNull LambdaExecutor executor, long period) { + CancelableTimeTask task = new CancelableTimeTask() { + @Override + public void execute() { + executor.execute(); + } + }; + + Thread thread = Thread.ofVirtual().start(() -> runTaskTimer(task, period)); + task.setThread(thread); + return task; + } + + public static @NotNull CancelableTimeTask runTaskAsync(@NotNull LambdaExecutor executor) { + CancelableTimeTask task = new CancelableTimeTask() { + @Override + public void execute() { + executor.execute(); + } + }; + + Thread thread = Thread.ofVirtual().start(task::execute); + task.setThread(thread); + return task; + } +} diff --git a/src/main/java/com.voinearadu/lambda/lambda/ArgLambdaExecutor.java b/src/main/java/com.voinearadu/lambda/lambda/ArgLambdaExecutor.java new file mode 100644 index 0000000..1f74233 --- /dev/null +++ b/src/main/java/com.voinearadu/lambda/lambda/ArgLambdaExecutor.java @@ -0,0 +1,5 @@ +package com.voinearadu.lambda.lambda; + +public interface ArgLambdaExecutor { + void execute(A a); +} \ No newline at end of file diff --git a/src/main/java/com.voinearadu/lambda/lambda/ArgsLambdaExecutor.java b/src/main/java/com.voinearadu/lambda/lambda/ArgsLambdaExecutor.java new file mode 100644 index 0000000..98eadc2 --- /dev/null +++ b/src/main/java/com.voinearadu/lambda/lambda/ArgsLambdaExecutor.java @@ -0,0 +1,6 @@ +package com.voinearadu.lambda.lambda; + + +public interface ArgsLambdaExecutor { + void execute(A a, B b); +} \ No newline at end of file diff --git a/src/main/java/com.voinearadu/lambda/lambda/LambdaExecutor.java b/src/main/java/com.voinearadu/lambda/lambda/LambdaExecutor.java new file mode 100644 index 0000000..da560da --- /dev/null +++ b/src/main/java/com.voinearadu/lambda/lambda/LambdaExecutor.java @@ -0,0 +1,5 @@ +package com.voinearadu.lambda.lambda; + +public interface LambdaExecutor { + void execute(); +} diff --git a/src/main/java/com.voinearadu/lambda/lambda/ReturnArgLambdaExecutor.java b/src/main/java/com.voinearadu/lambda/lambda/ReturnArgLambdaExecutor.java new file mode 100644 index 0000000..d0b4992 --- /dev/null +++ b/src/main/java/com.voinearadu/lambda/lambda/ReturnArgLambdaExecutor.java @@ -0,0 +1,6 @@ +package com.voinearadu.lambda.lambda; + + +public interface ReturnArgLambdaExecutor { + R execute(A a); +} \ No newline at end of file diff --git a/src/main/java/com.voinearadu/lambda/lambda/ReturnArgsLambdaExecutor.java b/src/main/java/com.voinearadu/lambda/lambda/ReturnArgsLambdaExecutor.java new file mode 100644 index 0000000..29cd3a9 --- /dev/null +++ b/src/main/java/com.voinearadu/lambda/lambda/ReturnArgsLambdaExecutor.java @@ -0,0 +1,6 @@ +package com.voinearadu.lambda.lambda; + + +public interface ReturnArgsLambdaExecutor { + R execute(A a, B b); +} \ No newline at end of file diff --git a/src/main/java/com.voinearadu/lambda/lambda/ReturnLambdaExecutor.java b/src/main/java/com.voinearadu/lambda/lambda/ReturnLambdaExecutor.java new file mode 100644 index 0000000..c7a8af2 --- /dev/null +++ b/src/main/java/com.voinearadu/lambda/lambda/ReturnLambdaExecutor.java @@ -0,0 +1,6 @@ +package com.voinearadu.lambda.lambda; + + +public interface ReturnLambdaExecutor { + R execute(); +} \ No newline at end of file diff --git a/src/main/java/com.voinearadu/logger/Logger.java b/src/main/java/com.voinearadu/logger/Logger.java new file mode 100644 index 0000000..c0c669b --- /dev/null +++ b/src/main/java/com.voinearadu/logger/Logger.java @@ -0,0 +1,95 @@ +package com.voinearadu.logger; + +import com.voinearadu.lambda.lambda.ArgLambdaExecutor; +import com.voinearadu.lambda.lambda.ReturnArgLambdaExecutor; +import com.voinearadu.logger.dto.ConsoleColor; +import com.voinearadu.logger.utils.StackTraceUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.slf4j.event.Level; + +public class Logger { + + private static final StackWalker STACK_WALKER = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); + private static Level LOG_LEVEl = Level.TRACE; + private static ReturnArgLambdaExecutor PACKAGE_PARSER = packageName -> null; + private static ArgLambdaExecutor LOG_HANDLER = log -> { + }; + + public static void setLogLevel(Level logLevel) { + Logger.LOG_LEVEl = logLevel; + } + + @SuppressWarnings("unused") + public static void setPackageParser(@NotNull ReturnArgLambdaExecutor packageParser) { + Logger.PACKAGE_PARSER = packageParser; + } + + public static void setLogHandler(@NotNull ArgLambdaExecutor logHandler) { + Logger.LOG_HANDLER = logHandler; + } + + private static @Nullable String parsePackage(String packageName) { + return PACKAGE_PARSER.execute(packageName); + } + + private static @NotNull Class getCallerClass(int steps) { + Class clazz = STACK_WALKER.walk(stack -> stack.map(StackWalker.StackFrame::getDeclaringClass).skip(steps).findFirst()).orElse(null); + + if (clazz == null) { + System.out.println(" Failed to get caller class "); + clazz = Logger.class; + } + + return clazz; + } + + public static void debug(Object object) { + log(Level.DEBUG, object, ConsoleColor.WHITE, 1); + } + + public static void good(Object object) { + log(Level.INFO, object, ConsoleColor.GREEN, 1); + } + + public static void log(Object object) { + log(Level.INFO, object, 1); + } + + public static void warn(Object object) { + log(Level.WARN, object, ConsoleColor.YELLOW, 1); + } + + public static void error(Object object) { + log(Level.ERROR, object, ConsoleColor.RED, 1); + } + + @SuppressWarnings("SameParameterValue") + private static void log(Level level, Object object, int depth) { + log(level, object, ConsoleColor.RESET, depth + 1); + } + + private static void log(Level level, Object object, @NotNull ConsoleColor color, int depth) { + if (level.toInt() < LOG_LEVEl.toInt()) { + return; + } + + Class caller = getCallerClass(depth + 2); + String id = parsePackage(caller.getPackageName()); + + if (id == null || id.isEmpty()) { + id = caller.getSimpleName() + ".java"; + } + + String log = switch (object) { + case null -> "null"; + case Throwable throwable -> StackTraceUtils.toString(throwable); + case StackTraceElement[] stackTraceElements -> StackTraceUtils.toString(stackTraceElements); + default -> object.toString(); + }; + + org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(id); + logger.info(color + log + ConsoleColor.RESET); + LOG_HANDLER.execute(log); + } +} diff --git a/src/main/java/com.voinearadu/logger/dto/ConsoleColor.java b/src/main/java/com.voinearadu/logger/dto/ConsoleColor.java new file mode 100644 index 0000000..95cb22b --- /dev/null +++ b/src/main/java/com.voinearadu/logger/dto/ConsoleColor.java @@ -0,0 +1,35 @@ +package com.voinearadu.logger.dto; + +import org.jetbrains.annotations.NotNull; + + +public enum ConsoleColor { + RESET("\033[0m"), + BLACK("\033[0;30m"), + RED("\033[0;31m"), + GREEN("\033[0;32m"), + YELLOW("\033[0;33m"), + BLUE("\033[0;34m"), + PURPLE("\033[0;35m"), + CYAN("\033[0;36m"), + WHITE("\033[0;37m"); + + private final @NotNull String code; + + ConsoleColor(@NotNull String code) { + this.code = code; + } + + @SuppressWarnings("unused") + public static @NotNull String clearString(@NotNull String log) { + for (ConsoleColor value : ConsoleColor.values()) { + log = log.replace(value.toString(), ""); + } + return log; + } + + @Override + public @NotNull String toString() { + return code; + } +} \ No newline at end of file diff --git a/src/main/java/com.voinearadu/logger/utils/StackTraceUtils.java b/src/main/java/com.voinearadu/logger/utils/StackTraceUtils.java new file mode 100644 index 0000000..ee3f367 --- /dev/null +++ b/src/main/java/com.voinearadu/logger/utils/StackTraceUtils.java @@ -0,0 +1,26 @@ +package com.voinearadu.logger.utils; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +public class StackTraceUtils { + + public static String toString(Throwable throwable) { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + PrintStream printStream = new PrintStream(outputStream); + + throwable.printStackTrace(printStream); + + return outputStream.toString(); + } + + public static String toString(StackTraceElement[] stackTrace) { + StringBuilder builder = new StringBuilder(); + + for (StackTraceElement element : stackTrace) { + builder.append(element.toString()).append('\n'); + } + + return builder.toString(); + } +} diff --git a/src/main/java/com.voinearadu/message_builder/GenericMessageBuilder.java b/src/main/java/com.voinearadu/message_builder/GenericMessageBuilder.java new file mode 100644 index 0000000..d8a7c42 --- /dev/null +++ b/src/main/java/com.voinearadu/message_builder/GenericMessageBuilder.java @@ -0,0 +1,116 @@ +package com.voinearadu.message_builder; + +import lombok.Getter; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + + +public abstract class GenericMessageBuilder { + + @Getter + protected T base; + protected List placeholders = new ArrayList<>(); + protected List values = new ArrayList<>(); + + + public GenericMessageBuilder(T base) { + this.base = base; + } + + protected GenericMessageBuilder(T base, List placeholders, List values) { + this.base = base; + this.placeholders = placeholders; + this.values = values; + } + + public GenericMessageBuilder parse(Map placeholders) { + GenericMessageBuilder working = this; + for (Object placeholder : placeholders.keySet()) { + String value = placeholders.get(placeholder).toString(); + working = working.parse(placeholder, value); + } + return working; + } + + public GenericMessageBuilder replace(Object placeholder, Object value) { + return parse(placeholder, value); + } + + public GenericMessageBuilder parse(Object placeholder, Object value) { + GenericMessageBuilder working = clone(); + + working.placeholders.add(placeholder); + working.values.add(value); + return working; + } + + public T parse() { + T parsed = base; + + for (int i = 0; i < Math.min(placeholders.size(), values.size()); i++) { + String placeholder = null; + Object placeholderObj = placeholders.get(i); + + String value = "null"; + Object valueObj = values.get(i); + + if (placeholderObj != null) { + placeholder = placeholderObj.toString(); + } + if (valueObj != null) { + value = valueObj.toString(); + } + + if (placeholder == null) { + continue; + } + + if (placeholder.startsWith("%") && placeholder.endsWith("%")) { + placeholder = placeholder.substring(1, placeholder.length() - 1); + } + + parsed = parsePlaceholder(parsed, "%" + placeholder + "%", value); + parsed = parsePlaceholder(parsed, "{" + placeholder + "}", value); + } + + if (MessageBuilderManager.instance().isChatColor()) { + parsed = parsePlaceholder(parsed, "&", "§"); + } + + this.base = parsed; + return parsed; + } + + @Override + public String toString() { + return convertToString(); + } + + /** + * Compares 2 instances of T and asserts if they are equal. + * + * @param o1 First instance of T. + * @param o2 Second instance of T. + * @return True if equal, false otherwise. + */ + @SuppressWarnings("unused") + protected abstract boolean equals(T o1, T o2); + + protected abstract String convertToString(); + + /** + * @param base the base where to parse into + * @param placeholder The placeholder to parse. Already has the identifier with % at beginning and end + * @param value The value to parse the placeholder with + * @return The parsed value + */ + protected abstract T parsePlaceholder(T base, String placeholder, String value); + + /** + * Clones the current object + */ + public abstract GenericMessageBuilder clone(); + +} diff --git a/src/main/java/com.voinearadu/message_builder/MessageBuilder.java b/src/main/java/com.voinearadu/message_builder/MessageBuilder.java new file mode 100644 index 0000000..21dfdef --- /dev/null +++ b/src/main/java/com.voinearadu/message_builder/MessageBuilder.java @@ -0,0 +1,51 @@ +package com.voinearadu.message_builder; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + + +public class MessageBuilder extends GenericMessageBuilder { + + public MessageBuilder(String base) { + super(base); + } + + protected MessageBuilder(String base, List placeholders, List values) { + super(base, placeholders, values); + } + + @Override + protected boolean equals(String o1, String o2) { + if (o1 == null) { + return o2 == null; + } + return o1.equals(o2); + } + + @Override + protected String convertToString() { + return parse(); + } + + @Override + protected String parsePlaceholder(String base, String placeholder, String value) { + if (base == null) { + return null; + } + return base.replace(placeholder, value); + } + + @Override + public GenericMessageBuilder clone() { + return new MessageBuilder(base, new ArrayList<>(placeholders), new ArrayList<>(values)); + } + + public MessageBuilder parse(Map placeholders) { + return (MessageBuilder) super.parse(placeholders); + } + + public MessageBuilder parse(Object placeholder, Object value) { + return (MessageBuilder) super.parse(placeholder, value); + } +} diff --git a/src/main/java/com.voinearadu/message_builder/MessageBuilderList.java b/src/main/java/com.voinearadu/message_builder/MessageBuilderList.java new file mode 100644 index 0000000..f212157 --- /dev/null +++ b/src/main/java/com.voinearadu/message_builder/MessageBuilderList.java @@ -0,0 +1,80 @@ +package com.voinearadu.message_builder; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + + +public class MessageBuilderList extends GenericMessageBuilder> { + + + /** + * Can use the separator %%new-line%% to split the message into multiple lines. + * + * @param base The base message to use. + */ + public MessageBuilderList(List base) { + super(base); + } + + public MessageBuilderList(List base, List placeholders, List values) { + super(base, placeholders, values); + } + + @Override + protected boolean equals(List o1, List o2) { + boolean output = true; + + if (o1 == null) { + return o2 == null; + } + + if (o1.size() != o2.size()) { + return false; + } + + for (int i = 0; i < o1.size(); i++) { + output = output && o1.get(i).equals(o2.get(i)); + } + + return output; + } + + @Override + protected String convertToString() { + List parsed = parse(); + StringBuilder output = new StringBuilder(); + + for (String line : parsed) { + output + .append(line) + .append('\n'); + } + + return output.toString(); + } + + @Override + protected List parsePlaceholder(List base, String placeholder, String value) { + if (base == null) { + return new ArrayList<>(); + } + + List output = new ArrayList<>(); + + for (String line : base) { + output.add(line.replace(placeholder, value)); + } + + return output; + } + + @Override + public MessageBuilderList clone() { + return new MessageBuilderList(base, new ArrayList<>(placeholders), new ArrayList<>(values)); + } + + public MessageBuilderList parse(Map placeholders) { + return (MessageBuilderList) super.parse(placeholders); + } +} diff --git a/src/main/java/com.voinearadu/message_builder/MessageBuilderManager.java b/src/main/java/com.voinearadu/message_builder/MessageBuilderManager.java new file mode 100644 index 0000000..3debb0d --- /dev/null +++ b/src/main/java/com.voinearadu/message_builder/MessageBuilderManager.java @@ -0,0 +1,30 @@ +package com.voinearadu.message_builder; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class MessageBuilderManager { + + private static MessageBuilderManager instance; + private boolean chatColor; + + public MessageBuilderManager(boolean chatColor) { + instance = this; + + this.chatColor = chatColor; + } + + public static void init(boolean chatColor) { + new MessageBuilderManager(chatColor); + } + + public static MessageBuilderManager instance() { + if (instance == null) { + new MessageBuilderManager(false); + } + return instance; + } + +} diff --git a/src/main/java/com.voinearadu/message_builder/data/MessageBuilderListTypeAdapter.java b/src/main/java/com.voinearadu/message_builder/data/MessageBuilderListTypeAdapter.java new file mode 100644 index 0000000..471d029 --- /dev/null +++ b/src/main/java/com.voinearadu/message_builder/data/MessageBuilderListTypeAdapter.java @@ -0,0 +1,46 @@ +package com.voinearadu.message_builder.data; + +import com.google.gson.JsonArray; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonElement; +import com.google.gson.JsonSerializationContext; +import com.voinearadu.file_manager.dto.GsonSerializer; +import com.voinearadu.message_builder.MessageBuilderList; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +@SuppressWarnings("unused") +public class MessageBuilderListTypeAdapter extends GsonSerializer { + + public MessageBuilderListTypeAdapter(ClassLoader classLoader) { + super(classLoader, MessageBuilderList.class); + } + + @Override + public MessageBuilderList deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext context) { + + JsonArray array = jsonElement.getAsJsonArray(); + List list = new ArrayList<>(); + + for (JsonElement element : array) { + list.add(element.getAsString()); + } + + return new MessageBuilderList(list); + } + + + @Override + public JsonElement serialize(MessageBuilderList src, Type type, JsonSerializationContext context) { + JsonArray array = new JsonArray(); + + for (String line : src.getBase()) { + array.add(line); + } + + return array; + } + +} diff --git a/src/main/java/com.voinearadu/message_builder/data/MessageBuilderTypeAdapter.java b/src/main/java/com.voinearadu/message_builder/data/MessageBuilderTypeAdapter.java new file mode 100644 index 0000000..7862fec --- /dev/null +++ b/src/main/java/com.voinearadu/message_builder/data/MessageBuilderTypeAdapter.java @@ -0,0 +1,29 @@ +package com.voinearadu.message_builder.data; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.voinearadu.file_manager.dto.GsonSerializer; +import com.voinearadu.message_builder.MessageBuilder; + +import java.lang.reflect.Type; + +@SuppressWarnings("unused") +public class MessageBuilderTypeAdapter extends GsonSerializer { + + public MessageBuilderTypeAdapter(ClassLoader classLoader) { + super(classLoader, MessageBuilder.class); + } + + @Override + public MessageBuilder deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext context) { + return new MessageBuilder(jsonElement.getAsString()); + } + + @Override + public JsonElement serialize(MessageBuilder src, Type type, JsonSerializationContext context) { + return new JsonPrimitive(src.getBase()); + } + +} diff --git a/src/main/java/com.voinearadu/redis_manager/Executor.java b/src/main/java/com.voinearadu/redis_manager/Executor.java new file mode 100644 index 0000000..306bfbc --- /dev/null +++ b/src/main/java/com.voinearadu/redis_manager/Executor.java @@ -0,0 +1,9 @@ +package com.voinearadu.redis_manager; + +public class Executor { + + public static void main(String[] args) { + new Main(); + } + +} diff --git a/src/main/java/com.voinearadu/redis_manager/Main.java b/src/main/java/com.voinearadu/redis_manager/Main.java new file mode 100644 index 0000000..1a43504 --- /dev/null +++ b/src/main/java/com.voinearadu/redis_manager/Main.java @@ -0,0 +1,33 @@ +package com.voinearadu.redis_manager; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.voinearadu.event_manager.EventManager; +import com.voinearadu.file_manager.manager.FileManager; +import com.voinearadu.message_builder.MessageBuilderManager; +import com.voinearadu.redis_manager.dto.RedisConfig; +import com.voinearadu.redis_manager.manager.DebugRedisManager; + +import java.util.List; + +public class Main { + + public Main() { + Gson gson = new GsonBuilder() + .setPrettyPrinting() + .disableHtmlEscaping() + .create(); + + FileManager fileManager = new FileManager(() -> gson, "config"); + MessageBuilderManager.init(true); + + RedisConfig config = fileManager.load(RedisConfig.class, ""); + + new DebugRedisManager(() -> gson, config, getClass().getClassLoader(), + new EventManager(), true, false, List.of( + "kingdoms_core_dev#*" + ) + ); + } + +} diff --git a/src/main/java/com.voinearadu/redis_manager/dto/RedisConfig.java b/src/main/java/com.voinearadu/redis_manager/dto/RedisConfig.java new file mode 100644 index 0000000..4cd430a --- /dev/null +++ b/src/main/java/com.voinearadu/redis_manager/dto/RedisConfig.java @@ -0,0 +1,28 @@ +package com.voinearadu.redis_manager.dto; + +import lombok.Getter; +import lombok.Setter; + +import java.util.UUID; + +@Getter +@Setter +public class RedisConfig { + + private String host = "127.0.0.1"; + private int port = 6379; + private String password = "password"; + private String channelBase = "channel"; + + // Advanced settings + private String redisID = UUID.randomUUID().toString(); + private int timeout = 2000; // 2s + private int waitBeforeIteration = 50; // 50ms + + public String getChannel() { + return channelBase + "#" + redisID; + } +} + + + diff --git a/src/main/java/com.voinearadu/redis_manager/dto/RedisResponse.java b/src/main/java/com.voinearadu/redis_manager/dto/RedisResponse.java new file mode 100644 index 0000000..bf46a5c --- /dev/null +++ b/src/main/java/com.voinearadu/redis_manager/dto/RedisResponse.java @@ -0,0 +1,85 @@ +package com.voinearadu.redis_manager.dto; + +import com.voinearadu.redis_manager.event.impl.ResponseEvent; +import com.voinearadu.redis_manager.manager.RedisManager; +import lombok.Getter; +import lombok.SneakyThrows; + +import java.util.List; + +@Getter +public class RedisResponse { + + protected final RedisManager redisManager; + + private final long id; + private T response; + private String responseClassName; + + // State + private boolean finished = false; + private boolean timeout = false; + + public RedisResponse(RedisManager redisManager, long id) { + this.redisManager = redisManager; + this.id = id; + } + + public void markAsFinished() { + finished = true; + } + + public void timeout() { + timeout = true; + } + + @SuppressWarnings("unused") + public boolean hasTimeout() { + return timeout; + } + + public void respond(T object, String responseClass) { + this.response = object; + this.responseClassName = responseClass; + markAsFinished(); + } + + public void respond(ResponseEvent response) { + if (response.getResponse().isEmpty() || response.getResponseClassName().isEmpty()) { + respond(null, response.getResponseClassName()); + return; + } + + if (response.getResponseClass().isAssignableFrom(List.class)) { + //noinspection unchecked + T object = (T) response.deserialize(); + respond(object, response.getResponseClassName()); + return; + } + + //noinspection unchecked + T object = (T) redisManager.getGson().execute().fromJson(response.getResponse(), response.getResponseClass()); + respond(object, response.getResponseClassName()); + } + + @SuppressWarnings("unused") + public T getResponse() { + return response; + } + + @SuppressWarnings({"unchecked", "unused"}) + @SneakyThrows + public Class getResponseClass() { + if (responseClassName == null) { + return null; + } + + return (Class) redisManager.getClassLoader().loadClass(responseClassName); + } + + @Override + public String toString() { + return redisManager.getGson().execute().toJson(this); + } + +} diff --git a/src/main/java/com.voinearadu/redis_manager/dto/gson/RedisRequestGsonTypeAdapter.java b/src/main/java/com.voinearadu/redis_manager/dto/gson/RedisRequestGsonTypeAdapter.java new file mode 100644 index 0000000..a2d5e07 --- /dev/null +++ b/src/main/java/com.voinearadu/redis_manager/dto/gson/RedisRequestGsonTypeAdapter.java @@ -0,0 +1,75 @@ +package com.voinearadu.redis_manager.dto.gson; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonSerializationContext; +import com.voinearadu.file_manager.dto.GsonSerializer; +import com.voinearadu.logger.Logger; +import com.voinearadu.redis_manager.event.RedisRequest; +import com.voinearadu.redis_manager.manager.RedisManager; + +import java.lang.reflect.Type; + + +@SuppressWarnings("rawtypes") +public class RedisRequestGsonTypeAdapter extends GsonSerializer { + + private final RedisManager redisManager; + + public RedisRequestGsonTypeAdapter(ClassLoader classLoader, RedisManager redisManager) { + super(classLoader, RedisRequest.class); + this.redisManager = redisManager; + } + + @Override + public RedisRequest deserialize(JsonElement json, Type type, JsonDeserializationContext context) { + try { + boolean __RedisEventTypeAdapter = json.getAsJsonObject().has("__RedisRequestTypeAdapter#deserialize"); + String className = json.getAsJsonObject().get("className").getAsString(); + + if (!__RedisEventTypeAdapter && !className.equals(RedisRequest.class.getName())) { + Class> clazz; + + try { + //noinspection unchecked + clazz = (Class>) classLoader.loadClass(className); + } catch (Throwable throwable) { + Logger.error("Class " + className + " was not found in the current JVM context. Please make sure" + + "the exact class exists in the project. If you want to have different classes in the sender and " + + "receiver override RedisEvent#getClassName and specify the class name there."); + return null; + } + + JsonObject object = json.getAsJsonObject(); + object.addProperty("__RedisRequestTypeAdapter#deserialize", true); + return context.deserialize(json, clazz); + } + + long id = json.getAsJsonObject().get("id").getAsLong(); + String originator = json.getAsJsonObject().get("originator").getAsString(); + String redisTarget = json.getAsJsonObject().get("redisTarget").getAsString(); + + return new RedisRequest(redisManager, className, id, originator, redisTarget); + } catch (Exception e) { + Logger.error("Error while deserializing RedisEvent"); + Logger.error("Json:"); + Logger.error(json.toString()); + //noinspection CallToPrintStackTrace + e.printStackTrace(); + return null; + } + } + + @Override + public JsonElement serialize(RedisRequest src, Type type, JsonSerializationContext context) { + JsonObject object = new JsonObject(); + + object.addProperty("className", src.getClassName()); + object.addProperty("id", src.getId()); + object.addProperty("originator", src.getOriginator()); + object.addProperty("redisTarget", src.getTarget()); + + return object; + } +} diff --git a/src/main/java/com.voinearadu/redis_manager/event/RedisBroadcast.java b/src/main/java/com.voinearadu/redis_manager/event/RedisBroadcast.java new file mode 100644 index 0000000..f35c76a --- /dev/null +++ b/src/main/java/com.voinearadu/redis_manager/event/RedisBroadcast.java @@ -0,0 +1,88 @@ +package com.voinearadu.redis_manager.event; + +import com.voinearadu.lambda.lambda.ArgLambdaExecutor; +import com.voinearadu.lambda.lambda.LambdaExecutor; +import com.voinearadu.redis_manager.dto.RedisResponse; +import com.voinearadu.redis_manager.manager.RedisManager; +import org.jetbrains.annotations.Nullable; + +public class RedisBroadcast extends RedisEvent { + + @SuppressWarnings("unused") + public RedisBroadcast(RedisManager redisManager, String className, long id, String originator, String target) { + super(redisManager, className, id, originator, target); + } + + @SuppressWarnings("unused") + public RedisBroadcast(RedisManager redisManager) { + super(redisManager, "*"); + } + + /** + * Do not call this method on this type of redis event. This event is only meant to be sent and not listen for any responses. + */ + @Override + @Deprecated(forRemoval = true) + public RedisResponse sendAndWait() { + return send(); + } + + /** + * Do not call this method on this type of redis event. This event is only meant to be sent and not listen for any responses. + */ + @Override + @Deprecated(forRemoval = true) + public void sendAndExecuteSync(ArgLambdaExecutor success, LambdaExecutor fail) { + send(); + success.execute(null); + } + + /** + * Do not call this method on this type of redis event. This event is only meant to be sent and not listen for any responses. + */ + @Override + @Deprecated(forRemoval = true) + public @Nullable Void sendAndGet(LambdaExecutor fail) { + send(); + return null; + } + + /** + * Do not call this method on this type of redis event. This event is only meant to be sent and not listen for any responses. + */ + @Override + @Deprecated(forRemoval = true) + public @Nullable Void sendAndGet() { + send(); + return null; + } + + /** + * Do not call this method on this type of redis event. This event is only meant to be sent and not listen for any responses. + */ + @Override + @Deprecated(forRemoval = true) + public void sendAndExecute(ArgLambdaExecutor success) { + send(); + success.execute(null); + } + + /** + * Do not call this method on this type of redis event. This event is only meant to be sent and not listen for any responses. + */ + @Override + @Deprecated(forRemoval = true) + public void sendAndExecute(ArgLambdaExecutor success, LambdaExecutor fail) { + send(); + success.execute(null); + } + + /** + * Do not call this method on this type of redis event. This event is only meant to be sent and not listen for any responses. + */ + @Override + @Deprecated(forRemoval = true) + public RedisResponse sendAndWait(int timeout) { + return send(); + } +} diff --git a/src/main/java/com.voinearadu/redis_manager/event/RedisEvent.java b/src/main/java/com.voinearadu/redis_manager/event/RedisEvent.java new file mode 100644 index 0000000..ffd88c6 --- /dev/null +++ b/src/main/java/com.voinearadu/redis_manager/event/RedisEvent.java @@ -0,0 +1,22 @@ +package com.voinearadu.redis_manager.event; + +import com.voinearadu.redis_manager.manager.RedisManager; +import org.jetbrains.annotations.NotNull; + +public class RedisEvent extends RedisRequest { + + public RedisEvent(@NotNull RedisManager redisManager, @NotNull String className, long id, @NotNull String originator, @NotNull String target) { + super(redisManager, className, id, originator, target); + } + + public RedisEvent(@NotNull RedisManager redisManager, @NotNull String target) { + super(redisManager, target); + } + + @Override + public void fireEvent() { + redisManager.getEventManager().fire(this); + respond(null); // Mark the event as completed + } + +} diff --git a/src/main/java/com.voinearadu/redis_manager/event/RedisRequest.java b/src/main/java/com.voinearadu/redis_manager/event/RedisRequest.java new file mode 100644 index 0000000..07d6894 --- /dev/null +++ b/src/main/java/com.voinearadu/redis_manager/event/RedisRequest.java @@ -0,0 +1,154 @@ +package com.voinearadu.redis_manager.event; + +import com.voinearadu.event_manager.dto.IEvent; +import com.voinearadu.lambda.ScheduleUtils; +import com.voinearadu.lambda.lambda.ArgLambdaExecutor; +import com.voinearadu.lambda.lambda.LambdaExecutor; +import com.voinearadu.redis_manager.dto.RedisResponse; +import com.voinearadu.redis_manager.event.impl.ResponseEvent; +import com.voinearadu.redis_manager.manager.RedisManager; +import lombok.Getter; +import lombok.Setter; +import lombok.SneakyThrows; +import org.jetbrains.annotations.Nullable; + +@Getter +public class RedisRequest implements IEvent { + + private final String className; + protected transient @Setter RedisManager redisManager; + private @Setter long id = -1; + private @Setter String originator = "UNKNOWN"; + private String target; + + public RedisRequest(RedisManager redisManager, String className, long id, String originator, String target) { + this.redisManager = redisManager; + this.className = className; + this.id = id; + this.originator = originator; + setTarget(target); + } + + public RedisRequest(RedisManager redisManager, String target) { + this.redisManager = redisManager; + this.className = getClass().getName(); + setTarget(target); + } + + public static @Nullable RedisRequest deserialize(RedisManager redisManager, String data) { + RedisRequest event = redisManager.getGson().execute().fromJson(data, RedisRequest.class); + + if(event == null) { + return null; + } + + event.setRedisManager(redisManager); + return event; + } + + public void setTarget(String target) { + if (target.contains("#")) { + this.target = target; + } else { + this.target = redisManager.getRedisConfig().getChannelBase() + "#" + target; + } + } + + /** + * Sends the event locally only + * Do NOT call manually, call {@link #sendAndWait()} or any of it derivatives + */ + public void fireEvent() { + redisManager.getEventManager().fire(this); + } + + @Override + public String toString() { + return redisManager.getGson().execute().toJson(this); + } + + @SuppressWarnings("unused") + public void respond(Response response) { + new ResponseEvent(redisManager, this, response).send(); + } + + @SuppressWarnings("UnusedReturnValue") + public RedisResponse send() { + return redisManager.send(this); + } + + public void sendAndExecuteSync(ArgLambdaExecutor success, LambdaExecutor fail) { + RedisResponse response = this.sendAndWait(); + + if (response.hasTimeout()) { + fail.execute(); + return; + } + + success.execute(response.getResponse()); + } + + public @Nullable Response sendAndGet(LambdaExecutor fail) { + RedisResponse response = this.sendAndWait(); + + if (response.hasTimeout()) { + fail.execute(); + return null; + } + + return response.getResponse(); + } + + @SuppressWarnings("unused") + public @Nullable Response sendAndGet() { + return sendAndGet(() -> { + }); + } + + @SuppressWarnings("unused") + public void sendAndExecute(ArgLambdaExecutor success) { + sendAndExecute(success, () -> { + }); + } + + public void sendAndExecute(ArgLambdaExecutor success, LambdaExecutor fail) { + ScheduleUtils.runTaskAsync(() -> sendAndExecuteSync(success, fail)); + } + + @SuppressWarnings({"unused", "UnusedReturnValue"}) + @SneakyThrows + public RedisResponse sendAndWait() { + return sendAndWait(redisManager.getRedisConfig().getTimeout()); + } + + @SuppressWarnings("BusyWait") + @SneakyThrows + public RedisResponse sendAndWait(int timeout) { + int currentWait = 0; + RedisResponse response = send(); + while (!response.isFinished()) { + Thread.sleep(redisManager.getRedisConfig().getWaitBeforeIteration()); + currentWait += redisManager.getRedisConfig().getWaitBeforeIteration(); + if (currentWait > timeout) { + response.timeout(); + break; + } + } + + redisManager.getAwaitingResponses().remove(response); + return response; + } + + @SuppressWarnings("unused") + public String getRedisTargetID() { + String[] split = target.split("#"); + return split[split.length - 1]; + } + + @SuppressWarnings("unused") + public String getOriginatorID() { + String[] split = originator.split("#"); + return split[split.length - 1]; + } + +} diff --git a/src/main/java/com.voinearadu/redis_manager/event/impl/PingEvent.java b/src/main/java/com.voinearadu/redis_manager/event/impl/PingEvent.java new file mode 100644 index 0000000..9568147 --- /dev/null +++ b/src/main/java/com.voinearadu/redis_manager/event/impl/PingEvent.java @@ -0,0 +1,13 @@ +package com.voinearadu.redis_manager.event.impl; + +import com.voinearadu.redis_manager.event.RedisRequest; +import com.voinearadu.redis_manager.manager.RedisManager; + + +@SuppressWarnings("unused") +public class PingEvent extends RedisRequest { + + public PingEvent(RedisManager redisManager, String target) { + super(redisManager, target); + } +} diff --git a/src/main/java/com.voinearadu/redis_manager/event/impl/ResponseEvent.java b/src/main/java/com.voinearadu/redis_manager/event/impl/ResponseEvent.java new file mode 100644 index 0000000..75818de --- /dev/null +++ b/src/main/java/com.voinearadu/redis_manager/event/impl/ResponseEvent.java @@ -0,0 +1,68 @@ +package com.voinearadu.redis_manager.event.impl; + +import com.google.gson.reflect.TypeToken; +import com.voinearadu.redis_manager.event.RedisRequest; +import com.voinearadu.redis_manager.manager.RedisManager; +import lombok.Getter; +import lombok.SneakyThrows; + +import java.util.ArrayList; +import java.util.List; + +@Getter +public class ResponseEvent extends RedisRequest { + + private static final String EMPTY_LIST = "EMPTY_LIST"; + + private final String response; + private final String responseClassName; + private String additionalData; + + public ResponseEvent(RedisManager redisManager, RedisRequest command, Object response) { + super(redisManager, command.getOriginator()); + this.setId(command.getId()); + + if (response == null) { + this.response = ""; + this.responseClassName = ""; + return; + } + + this.response = redisManager.getGson().execute().toJson(response); + this.responseClassName = response.getClass().getName(); + + if (response.getClass().isAssignableFrom(List.class)) { + ArrayList list = (ArrayList) response; + + if (list.isEmpty()) { + additionalData = EMPTY_LIST; + return; + } + + additionalData = list.getFirst().getClass().getName(); + } + } + + @SneakyThrows(value = {ClassNotFoundException.class}) + public Object deserialize() { + Class clazz = redisManager.getClassLoader().loadClass(responseClassName); + + if (clazz.isAssignableFrom(List.class)) { + if (additionalData.equals(EMPTY_LIST)) { + return new ArrayList<>(); + } + + Class aditionalClass = redisManager.getClassLoader().loadClass(additionalData); + + return redisManager.getGson().execute().fromJson(response, TypeToken.getParameterized(List.class, aditionalClass)); + } + + return redisManager.getGson().execute().fromJson(response, clazz); + } + + @SneakyThrows(value = {ClassNotFoundException.class}) + public Class getResponseClass() { + return redisManager.getClassLoader().loadClass(responseClassName); + } + +} diff --git a/src/main/java/com.voinearadu/redis_manager/manager/DebugRedisManager.java b/src/main/java/com.voinearadu/redis_manager/manager/DebugRedisManager.java new file mode 100644 index 0000000..05f14fc --- /dev/null +++ b/src/main/java/com.voinearadu/redis_manager/manager/DebugRedisManager.java @@ -0,0 +1,66 @@ +package com.voinearadu.redis_manager.manager; + +import com.google.gson.Gson; +import com.voinearadu.event_manager.EventManager; +import com.voinearadu.lambda.lambda.ReturnLambdaExecutor; +import com.voinearadu.logger.Logger; +import com.voinearadu.redis_manager.dto.RedisConfig; +import com.voinearadu.redis_manager.dto.RedisResponse; +import com.voinearadu.redis_manager.event.RedisRequest; +import lombok.Getter; +import redis.clients.jedis.JedisPubSub; + +import java.util.List; + +@Getter +public class DebugRedisManager extends RedisManager { + + private final List channels; + + public DebugRedisManager(ReturnLambdaExecutor gsonProvider, RedisConfig redisConfig, ClassLoader classLoader, EventManager eventManager, boolean debug, boolean localOnly, List channels) { + super(gsonProvider, redisConfig, classLoader, eventManager, debug, localOnly); + this.channels = channels; + } + + @Override + public RedisResponse send(RedisRequest event) { + Logger.log("Cannot sent events from DebugRedisManager"); + return new RedisResponse<>(this, 0); + } + + @Override + protected void subscribe() { + subscriberJedisPubSub = new JedisPubSub() { + + public void onMessage(String channel, final String command) { + try { + onMessageReceive(channel, command); + } catch (Throwable throwable) { + Logger.error("There was an error while receiving a message from Redis."); + Logger.error(throwable); + } + } + + public void onMessageReceive(String channel, final String event) { + getDebugger().receive(channel, event); + } + + @Override + public void onSubscribe(String channel, int subscribedChannels) { + getDebugger().subscribed(channel); + } + + @Override + public void onUnsubscribe(String channel, int subscribedChannels) { + getDebugger().unsubscribed(channel); + } + }; + + startRedisThread(); + } + + @Override + protected String[] getChannels() { + return channels.toArray(new String[0]); + } +} \ No newline at end of file diff --git a/src/main/java/com.voinearadu/redis_manager/manager/RedisDebugger.java b/src/main/java/com.voinearadu/redis_manager/manager/RedisDebugger.java new file mode 100644 index 0000000..6fc1cfe --- /dev/null +++ b/src/main/java/com.voinearadu/redis_manager/manager/RedisDebugger.java @@ -0,0 +1,98 @@ +package com.voinearadu.redis_manager.manager; + +import com.voinearadu.logger.Logger; +import com.voinearadu.message_builder.MessageBuilder; +import lombok.Getter; +import lombok.Setter; + + +@Getter +@Setter +public class RedisDebugger { + + private static final MessageBuilder creatingListener = new MessageBuilder("Creating RedisManager with listenID: %id%"); + private static final MessageBuilder receiveResponse = new MessageBuilder("[Receive-Response ] [%channel%] %response%"); + private static final MessageBuilder receive = new MessageBuilder("[Receive ] [%channel%] %event%"); + private static final MessageBuilder subscribed = new MessageBuilder("Subscribed to channel %channel%"); + private static final MessageBuilder unsubscribed = new MessageBuilder("Unsubscribed to channel %channel%"); + private static final MessageBuilder sendResponse = new MessageBuilder("[Send-Response ] [%channel%] %response%"); + private static final MessageBuilder send = new MessageBuilder("[Send ] [%channel%] %event%"); + private static final MessageBuilder registeringMethod = new MessageBuilder("Registering method %method% from class %class%"); + private boolean enabled; + + public RedisDebugger(boolean enabled) { + this.enabled = enabled; + } + + @SuppressWarnings("unused") + public void enable() { + enabled = true; + } + + @SuppressWarnings("unused") + public void disable() { + enabled = false; + } + + public void creatingListener(String id) { + print(creatingListener + .parse("id", id) + .parse()); + } + + public void receiveResponse(String channel, String response) { + print(receiveResponse + .parse("channel", channel) + .parse("response", response) + .parse()); + } + + public void receive(String channel, String event) { + print(receive + .parse("channel", channel) + .parse("event", event) + .parse()); + } + + public void subscribed(String channel) { + print(subscribed + .parse("channel", channel) + .parse()); + } + + public void unsubscribed(String channel) { + print(unsubscribed + .parse("channel", channel) + .parse()); + } + + public void sendResponse(String channel, String response) { + print(sendResponse + .parse("channel", channel) + .parse("response", response) + .parse()); + } + + public void send(String channel, String event) { + print(send + .parse("channel", channel) + .parse("event", event) + .parse()); + } + + @SuppressWarnings("unused") + public void registeringMethod(String method, String clazz) { + print(registeringMethod + .parse("method", method) + .parse("class", clazz) + .parse()); + } + + private void print(String message) { + if (!enabled) { + return; + } + Logger.log(message); + } + +} diff --git a/src/main/java/com.voinearadu/redis_manager/manager/RedisManager.java b/src/main/java/com.voinearadu/redis_manager/manager/RedisManager.java new file mode 100644 index 0000000..ee4ebc1 --- /dev/null +++ b/src/main/java/com.voinearadu/redis_manager/manager/RedisManager.java @@ -0,0 +1,269 @@ +package com.voinearadu.redis_manager.manager; + +import com.google.gson.Gson; +import com.voinearadu.event_manager.EventManager; +import com.voinearadu.lambda.ScheduleUtils; +import com.voinearadu.lambda.lambda.ArgLambdaExecutor; +import com.voinearadu.lambda.lambda.ReturnArgLambdaExecutor; +import com.voinearadu.lambda.lambda.ReturnLambdaExecutor; +import com.voinearadu.logger.Logger; +import com.voinearadu.redis_manager.dto.RedisConfig; +import com.voinearadu.redis_manager.dto.RedisResponse; +import com.voinearadu.redis_manager.event.RedisRequest; +import com.voinearadu.redis_manager.event.impl.ResponseEvent; +import lombok.Getter; +import org.jetbrains.annotations.Nullable; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.JedisPoolConfig; +import redis.clients.jedis.JedisPubSub; + +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; + +public class RedisManager { + + private static @Getter RedisManager instance; + + private final @Getter Queue> awaitingResponses; + private final @Getter EventManager eventManager; + private final @Getter RedisDebugger debugger; + private final @Getter RedisConfig redisConfig; + private final @Getter ClassLoader classLoader; + private final @Getter boolean localOnly; + private final @Getter ReturnLambdaExecutor gson; + protected @Getter JedisPubSub subscriberJedisPubSub; + private @Getter Thread redisTread; + private @Getter long id; + private JedisPool jedisPool; + + @SuppressWarnings("unused") + public T executeOnJedisAndGet(ReturnArgLambdaExecutor executor) { + return executeOnJedisAndGet(executor, error -> { + }); + } + + public T executeOnJedisAndGet(ReturnArgLambdaExecutor executor, ArgLambdaExecutor failExecutor) { + try (Jedis jedis = jedisPool.getResource()) { + return executor.execute(jedis); + } catch (Exception error) { + Logger.error(error); + failExecutor.execute(error); + return null; + } + } + + public void executeOnJedisAndForget(ArgLambdaExecutor executor) { + executeOnJedisAndForget(executor, error -> { + }); + } + + public void executeOnJedisAndForget(ArgLambdaExecutor executor, ArgLambdaExecutor failExecutor) { + try (Jedis jedis = jedisPool.getResource()) { + executor.execute(jedis); + } catch (Exception error) { + Logger.error(error); + failExecutor.execute(error); + } + } + + public RedisManager(ReturnLambdaExecutor gsonProvider, RedisConfig redisConfig, ClassLoader classLoader, EventManager eventManager) { + this(gsonProvider, redisConfig, classLoader, eventManager, false); + } + + public RedisManager(ReturnLambdaExecutor gsonProvider, RedisConfig redisConfig, ClassLoader classLoader, EventManager eventManager, boolean debug) { + this(gsonProvider, redisConfig, classLoader, eventManager, debug, false); + } + + public RedisManager(ReturnLambdaExecutor gsonProvider, RedisConfig redisConfig, ClassLoader classLoader, EventManager eventManager, boolean debug, boolean localOnly) { + instance = this; + + this.gson = gsonProvider; + this.redisConfig = redisConfig; + this.localOnly = localOnly; + this.classLoader = classLoader; + + this.debugger = new RedisDebugger(debug); + this.debugger.creatingListener(redisConfig.getChannel()); + this.eventManager = eventManager; + this.awaitingResponses = new ConcurrentLinkedQueue<>(); + + if (!localOnly) { + connectJedis(); + subscribe(); + } + } + + @SuppressWarnings("unused") + public void setDebug(boolean debug) { + debugger.setEnabled(debug); + } + + public RedisResponse send(RedisRequest event) { + event.setOriginator(redisConfig.getChannel()); + + if (event instanceof ResponseEvent) { + if (event.getTarget().equals(event.getOriginator())) { + debugger.sendResponse("LOCAL", gson.execute().toJson(event)); + eventManager.fire(event); + + ResponseEvent responseEvent = (ResponseEvent) event; + + RedisResponse response = getResponse(responseEvent); + if (response == null) { + return null; + } + response.respond(responseEvent); + + return null; + } + + debugger.sendResponse(event.getTarget(), gson.execute().toJson(event)); + + executeOnJedisAndForget(jedis -> + jedis.publish(event.getTarget(), gson.execute().toJson(event)) + ); + + return null; + } + + id++; + event.setId(id); + + RedisResponse redisResponse = new RedisResponse<>(this, event.getId()); + awaitingResponses.add(redisResponse); + + if (event.getTarget().equals(event.getOriginator())) { + debugger.send("LOCAL", gson.execute().toJson(event)); + eventManager.fire(event); + + return redisResponse; + } + + debugger.send(event.getTarget(), gson.execute().toJson(event)); + + executeOnJedisAndForget(jedis -> + jedis.publish(event.getTarget(), gson.execute().toJson(event)) + ); + + return redisResponse; + } + + private void connectJedis() { + if (jedisPool != null) { + jedisPool.destroy(); + } + + JedisPoolConfig jedisConfig = new JedisPoolConfig(); + jedisConfig.setMaxTotal(16); + + jedisPool = new JedisPool( + jedisConfig, + redisConfig.getHost(), + redisConfig.getPort(), + redisConfig.getTimeout(), + redisConfig.getPassword() + ); + } + + @SuppressWarnings("rawtypes") + @Nullable + private RedisResponse getResponse(ResponseEvent command) { + //Remove streams, these are slow when called a lot + for (RedisResponse response : awaitingResponses) { + if (response.getId() == command.getId()) { + return response; + } + } + + return null; + } + + protected void subscribe() { + Logger.log("[RedisManager] Subscribing to Redis: " + redisConfig.getHost() + ":" + redisConfig.getPort() + " @ " + redisConfig.getChannel()); + + RedisManager _this = this; + subscriberJedisPubSub = new JedisPubSub() { + + public void onMessage(String channel, final String command) { + try { + onMessageReceive(channel, command); + } catch (Throwable throwable) { + Logger.error("There was an error while receiving a message from Redis."); + Logger.error(throwable); + } + } + + public void onMessageReceive(String channel, final String event) { + if (event.isEmpty()) { + return; + } + + RedisRequest redisEvent = RedisRequest.deserialize(_this, event); + + if (redisEvent == null) { + Logger.warn("Received invalid RedisEvent: " + event); + return; + } + + if (redisEvent.getClass().equals(ResponseEvent.class)) { + ResponseEvent responseEvent = (ResponseEvent) redisEvent; + + debugger.receiveResponse(channel, event); + RedisResponse response = getResponse(responseEvent); + if (response == null) { + return; + } + response.respond(responseEvent); + + return; + } + + ScheduleUtils.runTaskAsync(() -> { + debugger.receive(channel, event); + redisEvent.fireEvent(); + }); + } + + @Override + public void onSubscribe(String channel, int subscribedChannels) { + debugger.subscribed(channel); + } + + @Override + public void onUnsubscribe(String channel, int subscribedChannels) { + debugger.unsubscribed(channel); + } + }; + + + startRedisThread(); + } + + protected void startRedisThread() { + if (redisTread != null) { + redisTread.interrupt(); + } + + redisTread = new Thread(() -> + executeOnJedisAndForget(jedis -> { + debugger.subscribed(redisConfig.getChannel()); + jedis.subscribe(subscriberJedisPubSub, getChannels()); + }, error -> { + Logger.error("Lost connection to redis server. Retrying in 3 seconds..."); + try { + Thread.sleep(3000); + } catch (InterruptedException ignored) { + } + + Logger.good("Reconnecting to redis server."); + startRedisThread(); + }) + ); + redisTread.start(); + } + + protected String[] getChannels() { + return new String[]{redisConfig.getChannel(), redisConfig.getChannelBase() + "#*"}; + } +} \ No newline at end of file diff --git a/src/main/java/com.voinearadu/reflections/Reflections.java b/src/main/java/com.voinearadu/reflections/Reflections.java new file mode 100644 index 0000000..98c2089 --- /dev/null +++ b/src/main/java/com.voinearadu/reflections/Reflections.java @@ -0,0 +1,219 @@ +package com.voinearadu.reflections; + +import com.voinearadu.logger.Logger; +import lombok.Getter; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.*; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +/** + * Every method in this class is very computationally expensive. It is recommended to cache the results, and if + * possible to use the class only in the constructors of modules and managers (one time uses). + */ +@Getter +public class Reflections { + + private final List zipFiles; + private final ClassLoader classLoader; + private final List searchDomain; + + public Reflections(List zipFiles, ClassLoader classLoader, String... searchDomain) { + this.zipFiles = zipFiles; + this.classLoader = classLoader; + this.searchDomain = Arrays.stream(searchDomain).map(domain -> { + if (!domain.endsWith(".")) { + return domain + "."; + } + return domain; + }).toList(); + } + + @SuppressWarnings("unused") + public static @Nullable Field getField(@NotNull Class clazz, String fieldName) { + Field field = null; + + try { + field = clazz.getField(fieldName); + } catch (NoSuchFieldException ignored) { + } + + if (field == null) { + try { + field = clazz.getDeclaredField(fieldName); + } catch (NoSuchFieldException ignored) { + } + } + + if (field != null) { + field.setAccessible(true); + } + + return field; + } + + public static @NotNull List getFields(@NotNull Class clazz) { + Set output = new HashSet<>(); + + Queue> classesToSearch = new LinkedList<>(); + classesToSearch.add(clazz); + + while (!classesToSearch.isEmpty()) { + Class searchClass = classesToSearch.poll(); + + if (searchClass == null) { + continue; + } + + classesToSearch.add(searchClass.getSuperclass()); + + for (Field field : searchClass.getDeclaredFields()) { + field.setAccessible(true); + output.add(field); + } + } + + return output.stream().toList(); + } + + @SuppressWarnings("unused") + public Reflections from(String... searchDomain) { + return new Reflections(zipFiles, classLoader, searchDomain); + } + + public @NotNull Set> getClasses() { + Set> classes = new HashSet<>(); + + for (File zipFile : zipFiles) { + classes.addAll(getClasses(zipFile)); + } + + return classes; + } + + private @NotNull Set> getClasses(File zipFile) { + Set> classes = new HashSet<>(); + + try (ZipFile zip = new ZipFile(zipFile)) { + Enumeration entries = zip.entries(); + + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + + if (entry == null) + break; + + if (entry.isDirectory()) { + continue; + } + + String name = entry.getName(); + + if (!name.endsWith(".class")) { + continue; + } + + name = name.replace("/", "."); + name = name.replace(".class", ""); + + if (searchDomain.stream().noneMatch(name::startsWith)) { + continue; + } + + String simpleClassName = name.substring(name.lastIndexOf('.') + 1); + + // Skip Mixin classes + if (simpleClassName.contains("Mixin")) { + continue; + } + + + try { + classes.add(classLoader.loadClass(name)); + } catch (Throwable throwable) { + Logger.error("Failed to load class " + name + " from " + zipFile.getName()); + Logger.error(throwable); + } + } + } catch (Exception error) { + Logger.error(error); + } + + return classes; + } + + @SuppressWarnings("unused") + public @NotNull Set> getTypesAnnotatedWith(@NotNull Class annotation) { + Set> classes = new HashSet<>(); + + for (Class clazz : getClasses()) { + if (clazz.getDeclaredAnnotation(annotation) != null) { + classes.add(clazz); + } + } + + return classes; + } + + @SuppressWarnings("unused") + public @NotNull Set getMethodsAnnotatedWith(@NotNull Class annotation) { + Set methods = new HashSet<>(); + + for (Class clazz : getClasses()) { + for (Method method : clazz.getDeclaredMethods()) { + if (method.getDeclaredAnnotation(annotation) != null) { + method.setAccessible(true); + methods.add(method); + } + } + } + + return methods; + } + + public @NotNull Set> getOfType(@NotNull Class clazz) { + Set> classes = new HashSet<>(); + + for (Class aClass : getClasses()) { + if (clazz.isAssignableFrom(aClass)) { + //noinspection unchecked + classes.add((Class) aClass); + } + } + + return classes; + } + + @SuppressWarnings("unused") + public static Method getCallingMethod(int depth) { + StackWalker.StackFrame stackFrame = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE) + .walk(stream -> stream + .skip(depth) + .findFirst() + .orElse(null) + ); + + if (stackFrame == null) { + throw new RuntimeException("StackFrame is null"); + } + + Class[] parameterTypes = stackFrame.getMethodType().parameterArray(); + Class clazz = stackFrame.getDeclaringClass(); + String methodName = stackFrame.getMethodName(); + + try { + return clazz.getMethod(methodName, parameterTypes); + } catch (NoSuchMethodException error) { + Logger.error(error); + } + + return null; + } + +} diff --git a/src/test/java/com/voinearadu/event_manager/EventManagerTests.java b/src/test/java/com/voinearadu/event_manager/EventManagerTests.java new file mode 100644 index 0000000..35e96a2 --- /dev/null +++ b/src/test/java/com/voinearadu/event_manager/EventManagerTests.java @@ -0,0 +1,45 @@ +package com.voinearadu.event_manager; + +import com.voinearadu.event_manager.dto.TestComplexEvent; +import com.voinearadu.event_manager.dto.TestEvent; +import com.voinearadu.event_manager.manager.TestEventListener; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class EventManagerTests { + + private static final EventManager eventManager = new EventManager(); + + public static boolean executed = false; + + @BeforeAll + public static void setup() { + eventManager.register(new TestEventListener()); + } + + @Test + public void testEvent() { + TestEvent event = new TestEvent(); + eventManager.fire(event); + + assertTrue(executed); + assertTrue(event.finished); + } + + @Test + public void testComplexEvent() { + TestComplexEvent event1 = new TestComplexEvent(1, 2); + TestComplexEvent event2 = new TestComplexEvent(10, 20); + + eventManager.fire(event1); + eventManager.fire(event2); + + assertEquals(3, event1.result); + assertEquals(30, event2.result); + } + + +} diff --git a/src/test/java/com/voinearadu/event_manager/dto/TestComplexEvent.java b/src/test/java/com/voinearadu/event_manager/dto/TestComplexEvent.java new file mode 100644 index 0000000..92f60aa --- /dev/null +++ b/src/test/java/com/voinearadu/event_manager/dto/TestComplexEvent.java @@ -0,0 +1,14 @@ +package com.voinearadu.event_manager.dto; + +public class TestComplexEvent implements IEvent { + + public int a; + public int b; + public int result; + + public TestComplexEvent(int a, int b) { + this.a = a; + this.b = b; + } + +} diff --git a/src/test/java/com/voinearadu/event_manager/dto/TestEvent.java b/src/test/java/com/voinearadu/event_manager/dto/TestEvent.java new file mode 100644 index 0000000..076e5ce --- /dev/null +++ b/src/test/java/com/voinearadu/event_manager/dto/TestEvent.java @@ -0,0 +1,7 @@ +package com.voinearadu.event_manager.dto; + +public class TestEvent implements IEvent { + + public boolean finished; + +} diff --git a/src/test/java/com/voinearadu/event_manager/manager/TestEventListener.java b/src/test/java/com/voinearadu/event_manager/manager/TestEventListener.java new file mode 100644 index 0000000..843ed0b --- /dev/null +++ b/src/test/java/com/voinearadu/event_manager/manager/TestEventListener.java @@ -0,0 +1,21 @@ +package com.voinearadu.event_manager.manager; + +import com.voinearadu.event_manager.EventManagerTests; +import com.voinearadu.event_manager.annotation.EventHandler; +import com.voinearadu.event_manager.dto.TestComplexEvent; +import com.voinearadu.event_manager.dto.TestEvent; + +public class TestEventListener { + + @EventHandler + public void onTestEvent(TestEvent event) { + EventManagerTests.executed = true; + event.finished = true; + } + + @EventHandler + public void onTestEvent(TestComplexEvent event) { + event.result = event.a + event.b; + } + +} diff --git a/src/test/java/com/voinearadu/file_manager/FileManagerTests.java b/src/test/java/com/voinearadu/file_manager/FileManagerTests.java new file mode 100644 index 0000000..7f791e4 --- /dev/null +++ b/src/test/java/com/voinearadu/file_manager/FileManagerTests.java @@ -0,0 +1,56 @@ +package com.voinearadu.file_manager; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.voinearadu.file_manager.dto.files.FileObject; +import com.voinearadu.file_manager.manager.FileManager; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.io.File; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class FileManagerTests { + + private static final String TEST_1 = "test1"; + + private static FileManager fileManager; + + @BeforeAll + public static void init() { + Gson gson = new GsonBuilder() //NOPMD - suppressed GsonCreatedForEachMethodCall + .create(); + fileManager = new FileManager(() -> gson, "tmp"); + } + + @AfterAll + public static void cleanup() { + deleteDirectory(fileManager.getDataFolder()); + } + + private static void deleteDirectory(File dir) { + File[] allContents = dir.listFiles(); + if (allContents != null) { + for (File file : allContents) { + deleteDirectory(file); + } + } + + //noinspection ResultOfMethodCallIgnored + dir.delete(); + } + + @Test + public void testObjectSaveLoad() { + FileObject object = new FileObject(101, TEST_1); + + fileManager.save(object); + + FileObject loadedObject = fileManager.load(FileObject.class); + + assertEquals(object.data1, loadedObject.data1); + assertEquals(object.data2, loadedObject.data2); + } +} diff --git a/src/test/java/com/voinearadu/file_manager/GsonTests.java b/src/test/java/com/voinearadu/file_manager/GsonTests.java new file mode 100644 index 0000000..70254b6 --- /dev/null +++ b/src/test/java/com/voinearadu/file_manager/GsonTests.java @@ -0,0 +1,205 @@ +package com.voinearadu.file_manager; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.voinearadu.file_manager.dto.gson.InterfaceGsonTypeAdapter; +import com.voinearadu.file_manager.dto.gson.SerializableListGsonTypeAdapter; +import com.voinearadu.file_manager.dto.gson.SerializableMapGsonTypeAdapter; +import com.voinearadu.file_manager.dto.gson.SerializableObjectTypeAdapter; +import com.voinearadu.file_manager.dto.interface_serialization.CustomObject1; +import com.voinearadu.file_manager.dto.interface_serialization.CustomObject2; +import com.voinearadu.file_manager.dto.serializable.ISerializable; +import com.voinearadu.file_manager.dto.serializable.SerializableList; +import com.voinearadu.file_manager.dto.serializable.SerializableMap; +import com.voinearadu.file_manager.dto.serializable.SerializableObject; +import lombok.Getter; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class GsonTests { + + private final static String listJson = "{\"class_name\":\"java.lang.String\",\"values\":[\"test1\",\"test2\",\"test3\"]}"; + private final static String objectJson = "{\"class_name\":\"java.lang.String\",\"data\":\"test\"}"; + private final static String object1Json = "{\"class_name\":\"com.voinearadu.file_manager.dto.interface_serialization.CustomObject1\",\"data\":\"test\"}"; + private final static String object2Json = "{\"class_name\":\"com.voinearadu.file_manager.dto.interface_serialization.CustomObject2\",\"data\":1000}"; + private static @Getter Gson gson = new Gson(); + + @BeforeAll + public static void init() { + ClassLoader classLoader = GsonTests.class.getClassLoader(); + + SerializableListGsonTypeAdapter serializableListGsonTypeAdapter = new SerializableListGsonTypeAdapter(classLoader); + SerializableMapGsonTypeAdapter serializableMapGsonTypeAdapter = new SerializableMapGsonTypeAdapter(classLoader); + SerializableObjectTypeAdapter serializableObjectTypeAdapter = new SerializableObjectTypeAdapter(classLoader); + + GsonBuilder gsonBuilder = new GsonBuilder() + .registerTypeAdapter(serializableListGsonTypeAdapter.getSerializedClass(), serializableListGsonTypeAdapter) + .registerTypeAdapter(serializableMapGsonTypeAdapter.getSerializedClass(), serializableMapGsonTypeAdapter) + .registerTypeAdapter(serializableObjectTypeAdapter.getSerializedClass(), serializableObjectTypeAdapter); + + //noinspection unchecked + for (InterfaceGsonTypeAdapter typeAdapter : InterfaceGsonTypeAdapter.generate(classLoader, CustomObject1.class, CustomObject2.class)) { + gsonBuilder.registerTypeAdapter(typeAdapter.getSerializedClass(), typeAdapter); + } + + gson = gsonBuilder.create(); //NOPMD - suppressed GsonCreatedForEachMethodCall + } + + @Test + public void serializeList() { + SerializableList serializableList = new SerializableList<>( + String.class, + List.of("test1", "test2", "test3") + ); + + String json = gson.toJson(serializableList); + + assertEquals(listJson, json); + } + + @Test + public void deserializeList() { + //noinspection unchecked + SerializableList serializableList = gson.fromJson(listJson, SerializableList.class); + + assertNotNull(serializableList); + assertEquals(String.class, serializableList.getValueClass()); + assertEquals(3, serializableList.size()); + assertEquals("test1", serializableList.get(0)); + assertEquals("test2", serializableList.get(1)); + assertEquals("test3", serializableList.get(2)); + } + + @Test + public void serializeDeserializeList() { + SerializableList serializableList = new SerializableList<>(String.class, List.of("test1", "test2", "test3")); + + String json = gson.toJson(serializableList); + + //noinspection unchecked + SerializableList serializableList2 = gson.fromJson(json, SerializableList.class); + + assertNotNull(serializableList2); + assertEquals(String.class, serializableList2.getValueClass()); + assertEquals(3, serializableList2.size()); + assertEquals("test1", serializableList2.get(0)); + assertEquals("test2", serializableList2.get(1)); + assertEquals("test3", serializableList2.get(2)); + } + + @Test + public void serializeMap() { + SerializableMap serializableMap = new SerializableMap<>(String.class, Integer.class, new HashMap<>() {{ + put("test1", 1); + put("test2", 2); + put("test3", 3); + }}); + + String json = gson.toJson(serializableMap); + + //noinspection unchecked + SerializableMap deserializedMap = (SerializableMap) gson.fromJson(json, SerializableMap.class); + + assertNotNull(deserializedMap); + + assertEquals(String.class, serializableMap.getKeyClass()); + assertEquals(String.class, deserializedMap.getKeyClass()); + + assertEquals(Integer.class, serializableMap.getValueClass()); + assertEquals(Integer.class, deserializedMap.getValueClass()); + + assertEquals(3, serializableMap.size()); + assertEquals(3, deserializedMap.size()); + + assertEquals(serializableMap.getOrDefault("test1", 0), deserializedMap.getOrDefault("test1", 1)); + assertEquals(serializableMap.getOrDefault("test2", 0), deserializedMap.getOrDefault("test2", 1)); + assertEquals(serializableMap.getOrDefault("test3", 0), deserializedMap.getOrDefault("test3", 1)); + } + + @Test + public void serializeDeserializeMap() { + SerializableMap serializableMap = new SerializableMap<>(String.class, Integer.class, new HashMap<>() {{ + put("test1", 1); + put("test2", 2); + put("test3", 3); + }}); + + String json = gson.toJson(serializableMap); + + //noinspection unchecked + SerializableMap serializableMap2 = gson.fromJson(json, SerializableMap.class); + + assertNotNull(serializableMap2); + assertEquals(String.class, serializableMap2.getKeyClass()); + assertEquals(Integer.class, serializableMap.getValueClass()); + assertEquals(3, serializableMap.size()); + assertEquals(1, serializableMap.get("test1")); + assertEquals(2, serializableMap.get("test2")); + assertEquals(3, serializableMap.get("test3")); + } + + + @Test + public void serializeObject() { + SerializableObject serializableObject = new SerializableObject<>(String.class, "test"); + + String json = gson.toJson(serializableObject); + + assertEquals(objectJson, json); + } + + @Test + public void deserializeObject() { + //noinspection unchecked + SerializableObject serializableObject = gson.fromJson(objectJson, SerializableObject.class); + + assertNotNull(serializableObject); + assertEquals(String.class, serializableObject.objectClass()); + assertEquals("test", serializableObject.object()); + } + + @Test + public void serializeDeserializeObject() { + SerializableObject serializableObject1 = new SerializableObject<>(String.class, "test"); + + String json = gson.toJson(serializableObject1); + + //noinspection unchecked + SerializableObject serializableObject2 = gson.fromJson(json, SerializableObject.class); + + assertNotNull(serializableObject2); + assertEquals(String.class, serializableObject2.objectClass()); + } + + @Test + public void serializeInterface() { + ISerializable customInterface1 = new CustomObject1("test"); + ISerializable customInterface2 = new CustomObject2(1000); + + String json1 = gson.toJson(customInterface1); + String json2 = gson.toJson(customInterface2); + + assertEquals(object1Json, json1); + assertEquals(object2Json, json2); + } + + @SuppressWarnings("EmptyMethod") + @Test + public void deserializeInterface() { + + } + + @SuppressWarnings("EmptyMethod") + @Test + public void serializeDeserializeInterface() { + + } + + +} diff --git a/src/test/java/com/voinearadu/file_manager/dto/files/FileObject.java b/src/test/java/com/voinearadu/file_manager/dto/files/FileObject.java new file mode 100644 index 0000000..e34c18f --- /dev/null +++ b/src/test/java/com/voinearadu/file_manager/dto/files/FileObject.java @@ -0,0 +1,13 @@ +package com.voinearadu.file_manager.dto.files; + +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +public class FileObject { + + public int data1; + public String data2; + +} diff --git a/src/test/java/com/voinearadu/file_manager/dto/interface_serialization/CustomInterface.java b/src/test/java/com/voinearadu/file_manager/dto/interface_serialization/CustomInterface.java new file mode 100644 index 0000000..c2a8096 --- /dev/null +++ b/src/test/java/com/voinearadu/file_manager/dto/interface_serialization/CustomInterface.java @@ -0,0 +1,6 @@ +package com.voinearadu.file_manager.dto.interface_serialization; + +import com.voinearadu.file_manager.dto.serializable.ISerializable; + +public interface CustomInterface extends ISerializable { +} diff --git a/src/test/java/com/voinearadu/file_manager/dto/interface_serialization/CustomObject1.java b/src/test/java/com/voinearadu/file_manager/dto/interface_serialization/CustomObject1.java new file mode 100644 index 0000000..7b81831 --- /dev/null +++ b/src/test/java/com/voinearadu/file_manager/dto/interface_serialization/CustomObject1.java @@ -0,0 +1,12 @@ +package com.voinearadu.file_manager.dto.interface_serialization; + +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public class CustomObject1 implements CustomInterface { + + @SuppressWarnings("unused") + private final String class_name = CustomObject1.class.getName(); + public String data; + +} diff --git a/src/test/java/com/voinearadu/file_manager/dto/interface_serialization/CustomObject2.java b/src/test/java/com/voinearadu/file_manager/dto/interface_serialization/CustomObject2.java new file mode 100644 index 0000000..61b43c4 --- /dev/null +++ b/src/test/java/com/voinearadu/file_manager/dto/interface_serialization/CustomObject2.java @@ -0,0 +1,12 @@ +package com.voinearadu.file_manager.dto.interface_serialization; + +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public class CustomObject2 implements CustomInterface { + + @SuppressWarnings("unused") + private final String class_name = CustomObject2.class.getName(); + public int data; + +} diff --git a/src/test/java/com/voinearadu/lambda/LambdaRunnableExecutorTest.java b/src/test/java/com/voinearadu/lambda/LambdaRunnableExecutorTest.java new file mode 100644 index 0000000..7c2b446 --- /dev/null +++ b/src/test/java/com/voinearadu/lambda/LambdaRunnableExecutorTest.java @@ -0,0 +1,80 @@ +package com.voinearadu.lambda; + +import com.voinearadu.lambda.lambda.*; +import lombok.SneakyThrows; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class LambdaRunnableExecutorTest { + + @BeforeAll + public static void init() { + } + + @Test + public void testLambdaExecutors() { + ArgLambdaExecutor> addEmpty = (list) -> list.add("empty"); + ArgsLambdaExecutor, String> add = List::add; + + ReturnLambdaExecutor getHelloWorld = () -> "Hello World"; + ReturnArgLambdaExecutor getHello = (arg) -> "Hello " + arg; + ReturnArgsLambdaExecutor concatenateStrings = (arg1, arg2) -> arg1 + arg2; + + List list = new ArrayList<>(); + + addEmpty.execute(list); + //noinspection ConstantValue + assertEquals(1, list.size()); + assertEquals("empty", list.getFirst()); + + add.execute(list, "test"); + assertEquals(2, list.size()); + assertEquals("test", list.get(1)); + + assertEquals("Hello World", getHelloWorld.execute()); + assertEquals("Hello test", getHello.execute("test")); + assertEquals("testtest", concatenateStrings.execute("test", "test")); + } + + @SneakyThrows + @Test + public void testRunTaskLater() { + AtomicBoolean executed = new AtomicBoolean(false); + + ScheduleUtils.runTaskLater(() -> executed.set(true), 1000); + + Thread.sleep(1500); + + assertTrue(executed.get()); + } + + @SneakyThrows + @Test + public void testRunTaskTimer() { + AtomicInteger executed = new AtomicInteger(0); + + ScheduleUtils.runTaskTimer(new CancelableTimeTask() { + @Override + public void execute() { + executed.getAndAdd(1); + + if (executed.get() == 5) { + this.cancel(); + } + } + }, 1000); + + Thread.sleep(7000); + + assertEquals(5, executed.get()); + } + +} diff --git a/src/test/java/com/voinearadu/logger/LoggerTest.java b/src/test/java/com/voinearadu/logger/LoggerTest.java new file mode 100644 index 0000000..034559c --- /dev/null +++ b/src/test/java/com/voinearadu/logger/LoggerTest.java @@ -0,0 +1,82 @@ +package com.voinearadu.logger; + +import lombok.SneakyThrows; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.slf4j.event.Level; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class LoggerTest { + + @SneakyThrows + @Test + public void testDebugLogger() { + List printed = new ArrayList<>(); + Logger.setLogLevel(Level.DEBUG); + Logger.setLogHandler(printed::add); + + Logger.debug("testDebugLogger#debug"); + Logger.log("testDebugLogger#log"); + Logger.good("testDebugLogger#good"); + Logger.warn("testDebugLogger#warn"); + Logger.error("testDebugLogger#error"); + + assertEquals(5, printed.size()); + } + + @SneakyThrows + @Test + public void testInfoLogger() { + List printed = new ArrayList<>(); + Logger.setLogLevel(Level.INFO); + Logger.setLogHandler(printed::add); + + Logger.debug("testInfoLogger#debug"); + Logger.log("testInfoLogger#log"); + Logger.good("testInfoLogger#good"); + Logger.warn("testInfoLogger#warn"); + Logger.error("testInfoLogger#error"); + + assertEquals(4, printed.size()); + } + + @SneakyThrows + @Test + public void testWarnLogger() { + List printed = new ArrayList<>(); + Logger.setLogLevel(Level.WARN); + Logger.setLogHandler(printed::add); + + Logger.debug("testWarnLogger#debug"); + Logger.log("testWarnLogger#log"); + Logger.good("testWarnLogger#good"); + Logger.warn("testWarnLogger#warn"); + Logger.error("testWarnLogger#error"); + + assertEquals(2, printed.size()); + } + + @SneakyThrows + @Test + public void testErrorLogger() { + List printed = new ArrayList<>(); + Logger.setLogLevel(Level.ERROR); + Logger.setLogHandler(printed::add); + + Logger.debug("testErrorLogger#debug"); + Logger.log("testErrorLogger#log"); + Logger.good("testErrorLogger#good"); + Logger.warn("testErrorLogger#warn"); + Logger.error("testErrorLogger#error"); + + assertEquals(1, printed.size()); + } + + +} diff --git a/src/test/java/com/voinearadu/message_builder/MessageBuilderTests.java b/src/test/java/com/voinearadu/message_builder/MessageBuilderTests.java new file mode 100644 index 0000000..d50fe20 --- /dev/null +++ b/src/test/java/com/voinearadu/message_builder/MessageBuilderTests.java @@ -0,0 +1,51 @@ +package com.voinearadu.message_builder; + + +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class MessageBuilderTests { + + @Test + public void testMessageBuilder() { + MessageBuilder builder1 = new MessageBuilder("This is a %placeholder%"); + MessageBuilder builder2 = new MessageBuilder("This is a %placeholder-1% with %placeholder-2%"); + + String result1 = builder1 + .parse("placeholder", "banana") + .parse(); + + String result2 = builder2 + .parse("placeholder-1", "banana") + .parse("%placeholder-2%", "1000 calories") + .parse(); + + assertEquals("This is a banana", result1); + assertEquals("This is a banana with 1000 calories", result2); + } + + @Test + public void testMessageBuilderList() { + MessageBuilderList builder = new MessageBuilderList(Arrays.asList( + "This is a %placeholder-1%", + "This %placeholder-1% has %placeholder-2%" + )); + + List result = builder + .parse("placeholder-1", "banana") + .parse("%placeholder-2%", "1000 calories") + .parse(); + + List expected = Arrays.asList( + "This is a banana", + "This banana has 1000 calories" + ); + assertArrayEquals(expected.toArray(), result.toArray()); + } + +} diff --git a/src/test/java/com/voinearadu/redis_manager/RedisTest.java b/src/test/java/com/voinearadu/redis_manager/RedisTest.java new file mode 100644 index 0000000..b1674fb --- /dev/null +++ b/src/test/java/com/voinearadu/redis_manager/RedisTest.java @@ -0,0 +1,133 @@ +package com.voinearadu.redis_manager; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.voinearadu.event_manager.EventManager; +import com.voinearadu.file_manager.dto.gson.SerializableListGsonTypeAdapter; +import com.voinearadu.file_manager.dto.gson.SerializableMapGsonTypeAdapter; +import com.voinearadu.file_manager.dto.gson.SerializableObjectTypeAdapter; +import com.voinearadu.message_builder.MessageBuilderManager; +import com.voinearadu.redis_manager.dto.RedisConfig; +import com.voinearadu.redis_manager.dto.RedisResponse; +import com.voinearadu.redis_manager.dto.event_serialization.ComplexEvent1; +import com.voinearadu.redis_manager.dto.event_serialization.SimpleEvent1; +import com.voinearadu.redis_manager.dto.event_serialization.SimpleEvent2; +import com.voinearadu.redis_manager.dto.gson.RedisRequestGsonTypeAdapter; +import com.voinearadu.redis_manager.event.RedisRequest; +import com.voinearadu.redis_manager.manager.RedisManager; +import com.voinearadu.redis_manager.manager.TestListener; +import lombok.Getter; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +public class RedisTest { + + private static RedisManager redisManager; + private static @Getter Gson gson; + + @BeforeAll + public static void init() { + MessageBuilderManager.init(false); + + ClassLoader classLoader = RedisTest.class.getClassLoader(); + + redisManager = new RedisManager(RedisTest::getGson, new RedisConfig(), classLoader, new EventManager(), true, true); + + RedisRequestGsonTypeAdapter redisRequestTypeAdapter = new RedisRequestGsonTypeAdapter(classLoader, redisManager); + SerializableListGsonTypeAdapter serializableListGsonTypeAdapter = new SerializableListGsonTypeAdapter(classLoader); + SerializableMapGsonTypeAdapter serializableMapGsonTypeAdapter = new SerializableMapGsonTypeAdapter(classLoader); + SerializableObjectTypeAdapter serializableObjectTypeAdapter = new SerializableObjectTypeAdapter(classLoader); + + + GsonBuilder gsonBuilder = new GsonBuilder() + .registerTypeAdapter(redisRequestTypeAdapter.getSerializedClass(), redisRequestTypeAdapter) + .registerTypeAdapter(serializableListGsonTypeAdapter.getSerializedClass(), serializableListGsonTypeAdapter) + .registerTypeAdapter(serializableMapGsonTypeAdapter.getSerializedClass(), serializableMapGsonTypeAdapter) + .registerTypeAdapter(serializableObjectTypeAdapter.getSerializedClass(), serializableObjectTypeAdapter); + + gson = gsonBuilder.create(); //NOPMD - suppressed GsonCreatedForEachMethodCall + + redisManager.getEventManager().register(TestListener.class); + } + + @Test + public void simpleEvent1() { + SimpleEvent1 event1 = new SimpleEvent1(redisManager, 10, 20); + RedisResponse result = event1.sendAndWait(); + + assertFalse(result.hasTimeout()); + assertTrue(result.isFinished()); + assertEquals(30, result.getResponse()); + } + + @Test + public void simpleEvent2() { + SimpleEvent2 event1 = new SimpleEvent2(redisManager, Arrays.asList("test1", "test2"), "-"); + RedisResponse result = event1.sendAndWait(); + + assertFalse(result.hasTimeout()); + assertTrue(result.isFinished()); + assertEquals("test1-test2-", result.getResponse()); + } + + @Test + public void complexEvent1() { + ComplexEvent1 event1 = new ComplexEvent1(redisManager, Arrays.asList("test1", "test2"), "test3"); + RedisResponse> result = event1.sendAndWait(); + + assertFalse(result.hasTimeout()); + assertTrue(result.isFinished()); + assertNotNull(result); + assertEquals(3, result.getResponse().size()); + assertEquals("test1", result.getResponse().get(0)); + assertEquals("test2", result.getResponse().get(1)); + assertEquals("test3", result.getResponse().get(2)); + } + + @Test + public void testGsonImplementation1() { + RedisRequest event = new RedisRequest<>(redisManager, "test"); + event.setId(100); + event.setOriginator("test_env"); + + String json = redisManager.getGson().execute().toJson(event); + + RedisRequest event2 = RedisRequest.deserialize(redisManager, json); + + assertNotNull(event2); + assertEquals(event.getClassName(), event2.getClassName()); + assertEquals(event.getId(), event2.getId()); + assertEquals(event.getOriginator(), event2.getOriginator()); + assertEquals(event.getTarget(), event2.getTarget()); + } + + @Test + public void testGsonImplementation2() { + ComplexEvent1 event = new ComplexEvent1(redisManager, Arrays.asList("test1", "test2"), "test3"); + event.setId(100); + event.setOriginator("test_env"); + + String json = redisManager.getGson().execute().toJson(event); + + RedisRequest event2 = RedisRequest.deserialize(redisManager, json); + + assertNotNull(event2); + assertEquals(event.getClassName(), event2.getClassName()); + assertEquals(event.getId(), event2.getId()); + assertEquals(event.getOriginator(), event2.getOriginator()); + assertEquals(event.getTarget(), event2.getTarget()); + assertInstanceOf(ComplexEvent1.class, event2); + + ComplexEvent1 event3 = (ComplexEvent1) event2; + + assertNotNull(event3); + assertEquals(event.getA(), event3.getA()); + assertEquals(event.getB(), event3.getB()); + } + +} diff --git a/src/test/java/com/voinearadu/redis_manager/dto/event_serialization/ComplexEvent1.java b/src/test/java/com/voinearadu/redis_manager/dto/event_serialization/ComplexEvent1.java new file mode 100644 index 0000000..d8af086 --- /dev/null +++ b/src/test/java/com/voinearadu/redis_manager/dto/event_serialization/ComplexEvent1.java @@ -0,0 +1,22 @@ +package com.voinearadu.redis_manager.dto.event_serialization; + +import com.voinearadu.redis_manager.event.RedisRequest; +import com.voinearadu.redis_manager.manager.RedisManager; +import lombok.Getter; + +import java.util.List; + +@Getter +public class ComplexEvent1 extends RedisRequest> { + + private final List a; + private final String b; + + public ComplexEvent1(RedisManager eventManager, List a, String b) { + super(eventManager, eventManager.getRedisConfig().getRedisID()); + + this.a = a; + this.b = b; + } + +} \ No newline at end of file diff --git a/src/test/java/com/voinearadu/redis_manager/dto/event_serialization/SimpleEvent1.java b/src/test/java/com/voinearadu/redis_manager/dto/event_serialization/SimpleEvent1.java new file mode 100644 index 0000000..ec1024b --- /dev/null +++ b/src/test/java/com/voinearadu/redis_manager/dto/event_serialization/SimpleEvent1.java @@ -0,0 +1,20 @@ +package com.voinearadu.redis_manager.dto.event_serialization; + +import com.voinearadu.redis_manager.event.RedisRequest; +import com.voinearadu.redis_manager.manager.RedisManager; +import lombok.Getter; + +@Getter +public class SimpleEvent1 extends RedisRequest { + + private final int a; + private final int b; + + public SimpleEvent1(RedisManager eventManager, int a, int b) { + super(eventManager, eventManager.getRedisConfig().getRedisID()); + + this.a = a; + this.b = b; + } + +} diff --git a/src/test/java/com/voinearadu/redis_manager/dto/event_serialization/SimpleEvent2.java b/src/test/java/com/voinearadu/redis_manager/dto/event_serialization/SimpleEvent2.java new file mode 100644 index 0000000..eab7b38 --- /dev/null +++ b/src/test/java/com/voinearadu/redis_manager/dto/event_serialization/SimpleEvent2.java @@ -0,0 +1,22 @@ +package com.voinearadu.redis_manager.dto.event_serialization; + +import com.voinearadu.redis_manager.event.RedisRequest; +import com.voinearadu.redis_manager.manager.RedisManager; +import lombok.Getter; + +import java.util.List; + +@Getter +public class SimpleEvent2 extends RedisRequest { + + private final List a; + private final String b; + + public SimpleEvent2(RedisManager eventManager, List a, String b) { + super(eventManager, eventManager.getRedisConfig().getRedisID()); + + this.a = a; + this.b = b; + } + +} diff --git a/src/test/java/com/voinearadu/redis_manager/manager/TestListener.java b/src/test/java/com/voinearadu/redis_manager/manager/TestListener.java new file mode 100644 index 0000000..c983675 --- /dev/null +++ b/src/test/java/com/voinearadu/redis_manager/manager/TestListener.java @@ -0,0 +1,37 @@ +package com.voinearadu.redis_manager.manager; + +import com.voinearadu.event_manager.annotation.EventHandler; +import com.voinearadu.redis_manager.dto.event_serialization.ComplexEvent1; +import com.voinearadu.redis_manager.dto.event_serialization.SimpleEvent1; +import com.voinearadu.redis_manager.dto.event_serialization.SimpleEvent2; + +import java.util.ArrayList; +import java.util.List; + +public class TestListener { + + @EventHandler + public void onSimpleEvent1(SimpleEvent1 event) { + event.respond(event.getA() + event.getB()); + } + + @EventHandler + public void onSimpleEvent2(SimpleEvent2 event) { + StringBuilder output = new StringBuilder(); + + for (String s : event.getA()) { + output.append(s).append(event.getB()); + } + + event.respond(output.toString()); + } + + @EventHandler + public void onComplexEvent1(ComplexEvent1 event) { + List output = new ArrayList<>(event.getA()); + output.add(event.getB()); + + event.respond(output); + } + +}