port RedstonerPlots a bit
This commit is contained in:
+46
-5
@@ -1,7 +1,14 @@
|
|||||||
|
import com.github.jengelman.gradle.plugins.shadow.relocation.RelocateClassContext
|
||||||
|
import com.github.jengelman.gradle.plugins.shadow.relocation.RelocatePathContext
|
||||||
|
import com.github.jengelman.gradle.plugins.shadow.relocation.Relocator
|
||||||
|
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||||
|
import com.github.jengelman.gradle.plugins.shadow.transformers.TransformerContext
|
||||||
import org.jetbrains.kotlin.gradle.dsl.Coroutines
|
import org.jetbrains.kotlin.gradle.dsl.Coroutines
|
||||||
|
import shadow.org.apache.tools.zip.ZipOutputStream
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm") version "1.2.51"
|
kotlin("jvm") version "1.2.51"
|
||||||
|
id("com.github.johnrengelman.plugin-shadow") version "2.0.3"
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "io.dico"
|
group = "io.dico"
|
||||||
@@ -9,24 +16,58 @@ version = "0.1"
|
|||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
mavenLocal()
|
||||||
maven("https://dl.bintray.com/kotlin/exposed")
|
maven("https://dl.bintray.com/kotlin/exposed")
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlin.experimental.coroutines = Coroutines.ENABLE
|
kotlin.experimental.coroutines = Coroutines.ENABLE
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile(files("../res/spigot-1.13-pre7.jar"))
|
|
||||||
compile(kotlin("stdlib-jdk8"))
|
compile(kotlin("stdlib-jdk8"))
|
||||||
|
|
||||||
|
shadow(files("../res/spigot-1.13-pre7.jar"))
|
||||||
|
|
||||||
|
compile("io.dico.dicore:dicore3-core:1.2-mc-1.13")
|
||||||
|
compile("io.dico.dicore:dicore3-command:1.2-mc-1.13")
|
||||||
|
|
||||||
compile("org.jetbrains.exposed:exposed:0.10.3")
|
compile("org.jetbrains.exposed:exposed:0.10.3")
|
||||||
compile("org.jetbrains.kotlinx:kotlinx-coroutines-core:0.23.4")
|
compile("org.jetbrains.kotlinx:kotlinx-coroutines-core:0.23.4")
|
||||||
|
compile("com.zaxxer:HikariCP:3.2.0")
|
||||||
|
|
||||||
|
val jacksonVersion = "2.9.6"
|
||||||
|
compile("com.fasterxml.jackson.core:jackson-core:$jacksonVersion")
|
||||||
|
compile("com.fasterxml.jackson.core:jackson-databind:$jacksonVersion")
|
||||||
|
compile("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:$jacksonVersion")
|
||||||
|
compile("com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion")
|
||||||
|
|
||||||
|
compile("org.slf4j:slf4j-api:1.7.25")
|
||||||
|
compile("ch.qos.logback:logback-classic:1.2.3")
|
||||||
|
|
||||||
testCompile("junit:junit:4.12")
|
testCompile("junit:junit:4.12")
|
||||||
}
|
}
|
||||||
|
|
||||||
val jar by tasks.getting(Jar::class)
|
tasks {
|
||||||
val fatJar by tasks.creating(Jar::class) {
|
val jar by getting(Jar::class)
|
||||||
|
|
||||||
|
val fatJar by creating(Jar::class) {
|
||||||
|
destinationDir = file("$rootDir/debug/plugins")
|
||||||
baseName = "parcels2-all"
|
baseName = "parcels2-all"
|
||||||
manifest.attributes["Main-Class"] = ""
|
|
||||||
destinationDir = file("debug/plugins")
|
|
||||||
from(*configurations.compile.map(::zipTree).toTypedArray())
|
from(*configurations.compile.map(::zipTree).toTypedArray())
|
||||||
with(jar)
|
with(jar)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val shadowJar by getting(ShadowJar::class) {
|
||||||
|
destinationDir = file("$rootDir/debug/plugins")
|
||||||
|
baseName = "parcels2-shaded"
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
exclude(dependency(files("../res/spigot-1.13-pre7.jar")))
|
||||||
|
}
|
||||||
|
|
||||||
|
relocate("", "io.dico.parcels2.util.") {
|
||||||
|
exclude("META-INF/*")
|
||||||
|
exclude("logback*")
|
||||||
|
exclude("plugin*")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,780 +0,0 @@
|
|||||||
package io.dico.dicore;
|
|
||||||
|
|
||||||
import io.dico.dicore.exceptions.ExceptionHandler;
|
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.lang.reflect.Modifier;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reflective utilities
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public class Reflection {
|
|
||||||
private static final ExceptionHandler exceptionHandler;
|
|
||||||
private static final Field fieldModifiersField = restrictedSearchField(Field.class, "modifiers");
|
|
||||||
private static Consumer<String> errorTarget;
|
|
||||||
|
|
||||||
private Reflection() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static {
|
|
||||||
exceptionHandler = new ExceptionHandler() {
|
|
||||||
@Override
|
|
||||||
public void handle(Throwable ex) {
|
|
||||||
handleGenericException(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object handleGenericException(Throwable ex, Object... args) {
|
|
||||||
String action = args.length == 0 || !(args[0] instanceof String) ? "executing a reflective operation" : (String) args[0];
|
|
||||||
ExceptionHandler.log(errorTarget, action, ex);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// don't use method reference here: the current reference in System.out would be cached.
|
|
||||||
setErrorTarget(msg -> System.out.println(msg));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the output where ReflectiveOperationException's and similar are sent.
|
|
||||||
* This defaults to {@link System#out}.
|
|
||||||
*
|
|
||||||
* @param target The new output
|
|
||||||
* @throws NullPointerException if target is null
|
|
||||||
*/
|
|
||||||
public static void setErrorTarget(Consumer<String> target) {
|
|
||||||
errorTarget = Objects.requireNonNull(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This search modifier tells the implementation that it should subsequently search superclasses for the field/method.
|
|
||||||
* Using this modifier means a call to {@link #deepSearchField(Class, String)} will be used instead of {@link #restrictedSearchField(Class, String)}
|
|
||||||
* and a call to {@link #deepSearchMethod(Class, String, Class[])} will be used instead of {@link #restrictedSearchMethod(Class, String, Class[])}
|
|
||||||
*/
|
|
||||||
public static final int DEEP_SEARCH = 0x1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This search modifier applies only to fields, and tells the implementation that a final modifier might be present on a found field, and that it should be removed.
|
|
||||||
*/
|
|
||||||
public static final int REMOVE_FINAL = 0x2;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This search modifier applies only to methods, and tells the implementation that it should completely ignore parameter types and return the first method with a matching name
|
|
||||||
* The implementation uses {@link Class#getDeclaredMethods()} instead of {@link Class#getDeclaredMethod(String, Class[])} if this modifier is set.
|
|
||||||
*/
|
|
||||||
public static final int IGNORE_PARAMS = 0x2;
|
|
||||||
|
|
||||||
/*
|
|
||||||
### FIELD METHODS ###
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Search a field of any accessibility within the class or any of its superclasses.
|
|
||||||
* The first field with the given name that is found will be returned.
|
|
||||||
* <p>
|
|
||||||
* If a field is found and it is not accessible, this method attempts to make it accessible.
|
|
||||||
* If a {@link SecurityException} is thrown in the process, that is ignored and the field will be returned nonetheless.
|
|
||||||
* <p>
|
|
||||||
* This method throws IllegalArgumentException if the Field is not found, because, in most cases, that should never happen,
|
|
||||||
* and it should simplify debugging. In some cases, if you want to know if the field exists, you'll have to use try/catch for that.
|
|
||||||
*
|
|
||||||
* @param clazz The lowest class in the ladder to start searching from
|
|
||||||
* @param fieldName The name of the field
|
|
||||||
* //@param fieldType the type of the field, or null if it can be any.
|
|
||||||
* @return The field
|
|
||||||
* @throws NullPointerException if clazz is null or fieldName is null
|
|
||||||
* @throws IllegalArgumentException if the field doesn't exist
|
|
||||||
* @see #restrictedSearchField(Class, String)
|
|
||||||
*/
|
|
||||||
public static Field deepSearchField(Class<?> clazz, String fieldName/*, Class<?> fieldType*/) {
|
|
||||||
Class<?> currentClass = clazz;
|
|
||||||
Field result;
|
|
||||||
do {
|
|
||||||
// throws NPE if class or fieldName is null
|
|
||||||
result = internalSearchField(clazz, fieldName);
|
|
||||||
if (result != null) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
currentClass = currentClass.getSuperclass();
|
|
||||||
} while (currentClass != null);
|
|
||||||
|
|
||||||
throw new IllegalArgumentException("field not found in " + clazz.getCanonicalName() + " and superclasses: " + fieldName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Search a field of any accessibility within the class, but not its superclasses.
|
|
||||||
* <p>
|
|
||||||
* If a field is found and it is not accessible, this method attempts to make it accessible.
|
|
||||||
* If a {@link SecurityException} is thrown in the process, that is ignored and the field will be returned nonetheless.
|
|
||||||
* <p>
|
|
||||||
* This method throws IllegalArgumentException if the Field is not found, because, in most cases, that should never happen,
|
|
||||||
* and it should simplify debugging. In some cases, if you want to know if the field exists, you'll have to use try/catch for that.
|
|
||||||
*
|
|
||||||
* @param clazz The only class to search for the field
|
|
||||||
* @param fieldName The name of the field
|
|
||||||
* @return The field
|
|
||||||
* @throws NullPointerException if clazz or fieldName is null
|
|
||||||
* @throws IllegalArgumentException if the field does not exist
|
|
||||||
*/
|
|
||||||
public static Field restrictedSearchField(Class<?> clazz, String fieldName) {
|
|
||||||
Field result = internalSearchField(clazz, fieldName);
|
|
||||||
if (result == null) {
|
|
||||||
throw new IllegalArgumentException("field not found in " + clazz.getCanonicalName() + ": " + fieldName);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Searches for a field using the given search method.
|
|
||||||
*
|
|
||||||
* @param modifiers The modifiers for field search. Can have {@link #DEEP_SEARCH} and {@link #REMOVE_FINAL}
|
|
||||||
* @param clazz The class to search in/from
|
|
||||||
* @param fieldName Name of the field
|
|
||||||
* @return The field
|
|
||||||
* @throws NullPointerException if clazz or fieldName is null
|
|
||||||
* @throws IllegalArgumentException if the field is not found
|
|
||||||
*/
|
|
||||||
public static Field searchField(int modifiers, Class<?> clazz, String fieldName) {
|
|
||||||
Field result;
|
|
||||||
if ((modifiers & DEEP_SEARCH) != 0) {
|
|
||||||
result = deepSearchField(clazz, fieldName);
|
|
||||||
} else {
|
|
||||||
result = restrictedSearchField(clazz, fieldName);
|
|
||||||
}
|
|
||||||
if ((modifiers & REMOVE_FINAL) != 0) {
|
|
||||||
removeFinalModifier(result);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The same as {@link #restrictedSearchField(Class, String)}, but returns null instead of throwing IllegalArgumentException
|
|
||||||
* @see #restrictedSearchField(Class, String)
|
|
||||||
*/
|
|
||||||
private static Field internalSearchField(Class<?> clazz, String fieldName) {
|
|
||||||
Field result;
|
|
||||||
try {
|
|
||||||
// throws NullPointerException if either clazz or fieldName are null.
|
|
||||||
result = clazz.getDeclaredField(fieldName);
|
|
||||||
} catch (NoSuchFieldException | SecurityException ex) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!result.isAccessible()) try {
|
|
||||||
result.setAccessible(true);
|
|
||||||
} catch (SecurityException ignored) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempts to remove existing final modifier of the given field
|
|
||||||
* This method should always return true.
|
|
||||||
*
|
|
||||||
* @param field The field whose final modifier to remove
|
|
||||||
* @return true if the field most definitely has no final modifier after this call
|
|
||||||
* @throws NullPointerException if field is null
|
|
||||||
*/
|
|
||||||
public static boolean removeFinalModifier(Field field) {
|
|
||||||
Objects.requireNonNull(field);
|
|
||||||
try {
|
|
||||||
int modifiers = (int) fieldModifiersField.get(field);
|
|
||||||
if (modifiers != (modifiers &= ~Modifier.FINAL)) {
|
|
||||||
fieldModifiersField.set(field, modifiers);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} catch (Exception ex) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets field value of the field named fieldName and the given instance
|
|
||||||
* To find the field, {@link #deepSearchField(Class, String)} is used (DEEP search method).
|
|
||||||
* <p>
|
|
||||||
* If a {@link ReflectiveOperationException} occurs, this is printed to {@link System#out}
|
|
||||||
*
|
|
||||||
* @param instance The instance whose field value to get
|
|
||||||
* @param fieldName the name of the field
|
|
||||||
* @param <T> The expected/known field type
|
|
||||||
* @return The field value
|
|
||||||
* @throws IllegalArgumentException if the field doesn't exist
|
|
||||||
* @see #deepSearchField(Class, String)
|
|
||||||
* @see #getFieldValue(Class, String, Object)
|
|
||||||
*/
|
|
||||||
public static <T> T getFieldValue(Object instance, String fieldName) {
|
|
||||||
return getFieldValue(deepSearchField(instance.getClass(), fieldName), instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets field value of the field named fieldName and the given instance
|
|
||||||
* To find the field, {@link #restrictedSearchField(Class, String)} is used (RESTRICTED search method).
|
|
||||||
* <p>
|
|
||||||
* If a {@link ReflectiveOperationException} occurs, this is printed to {@link System#out}
|
|
||||||
*
|
|
||||||
* @param clazz The class to search for the field
|
|
||||||
* @param instance The instance whose field value to get
|
|
||||||
* @param fieldName the name of the field
|
|
||||||
* @param <T> The expected/known field type
|
|
||||||
* @return The field value
|
|
||||||
* @throws IllegalArgumentException if the field doesn't exist
|
|
||||||
* @see #restrictedSearchField(Class, String)
|
|
||||||
* @see #getFieldValue(Field, Object)
|
|
||||||
*/
|
|
||||||
public static <T> T getFieldValue(Class<?> clazz, String fieldName, Object instance) {
|
|
||||||
return getFieldValue(restrictedSearchField(clazz, fieldName), instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets field value of the field named fieldName and the given instance
|
|
||||||
* To find the field, {@link #searchField(int, Class, String)} is used.
|
|
||||||
* <p>
|
|
||||||
* If a {@link ReflectiveOperationException} occurs, this is printed to {@link System#out}
|
|
||||||
*
|
|
||||||
* @param modifiers The modifiers for field search. Can have {@link #DEEP_SEARCH} and {@link #REMOVE_FINAL}
|
|
||||||
* @param clazz The class to search for the field
|
|
||||||
* @param instance The instance whose field value to get
|
|
||||||
* @param fieldName the name of the field
|
|
||||||
* @param <T> The expected/known field type
|
|
||||||
* @return The field value
|
|
||||||
* @throws IllegalArgumentException if the field doesn't exist
|
|
||||||
* @see #searchField(int, Class, String)
|
|
||||||
* @see #getFieldValue(Field, Object)
|
|
||||||
*/
|
|
||||||
public static <T> T getFieldValue(int modifiers, Class<?> clazz, String fieldName, Object instance) {
|
|
||||||
return getFieldValue(searchField(modifiers, clazz, fieldName), instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets field value of the given field and the given instance
|
|
||||||
* <p>
|
|
||||||
* If a {@link ReflectiveOperationException} occurs, this is printed to {@link System#out}
|
|
||||||
*
|
|
||||||
* @param field the field
|
|
||||||
* @param instance The instance whose field value to get
|
|
||||||
* @param <T> The expected/known field type
|
|
||||||
* @return The field value
|
|
||||||
*/
|
|
||||||
public static <T> T getFieldValue(Field field, Object instance) {
|
|
||||||
return exceptionHandler.supplySafe(() -> (T) field.get(instance));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets static field value of the field named fieldName
|
|
||||||
* To find the field, {@link #restrictedSearchField(Class, String)} is used (RESTRICTED search method).
|
|
||||||
* <p>
|
|
||||||
* If a {@link ReflectiveOperationException} occurs, this is printed to {@link System#out}
|
|
||||||
*
|
|
||||||
* @param clazz The class to search for the field
|
|
||||||
* @param fieldName the name of the field
|
|
||||||
* @param <T> The expected/known field type
|
|
||||||
* @return The field value
|
|
||||||
* @throws IllegalArgumentException if the field doesn't exist
|
|
||||||
* @see #restrictedSearchField(Class, String)
|
|
||||||
* @see #getStaticFieldValue(Field)
|
|
||||||
*/
|
|
||||||
public static <T> T getStaticFieldValue(Class<?> clazz, String fieldName) {
|
|
||||||
return getStaticFieldValue(restrictedSearchField(clazz, fieldName));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets static field value of the field named fieldName
|
|
||||||
* To find the field, {@link #searchField(int, Class, String)} is used.
|
|
||||||
* <p>
|
|
||||||
* If a {@link ReflectiveOperationException} occurs, this is printed to {@link System#out}
|
|
||||||
*
|
|
||||||
* @param modifiers The modifiers for field search. Can have {@link #DEEP_SEARCH} and {@link #REMOVE_FINAL}
|
|
||||||
* @param clazz The class to search for the field
|
|
||||||
* @param fieldName the name of the field
|
|
||||||
* @param <T> The expected/known field type
|
|
||||||
* @return The field value
|
|
||||||
* @throws IllegalArgumentException if the field doesn't exist
|
|
||||||
* @see #deepSearchField(Class, String)
|
|
||||||
* @see #getStaticFieldValue(Field)
|
|
||||||
*/
|
|
||||||
public static <T> T getStaticFieldValue(int modifiers, Class<?> clazz, String fieldName) {
|
|
||||||
return getStaticFieldValue(searchField(modifiers, clazz, fieldName));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets static field value
|
|
||||||
* <p>
|
|
||||||
* If a {@link ReflectiveOperationException} occurs, this is printed to {@link System#out}
|
|
||||||
* <p>
|
|
||||||
* Equivalent to the call {@code getFieldValue(field, (Object) null)}
|
|
||||||
*
|
|
||||||
* @param field the field
|
|
||||||
* @param <T> The expected/known field type
|
|
||||||
* @return The field value
|
|
||||||
* @see #getFieldValue(Field, Object)
|
|
||||||
*/
|
|
||||||
public static <T> T getStaticFieldValue(Field field) {
|
|
||||||
return getFieldValue(field, (Object) null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets field value of the field named fieldName and the given instance
|
|
||||||
* To find the field, {@link #deepSearchField(Class, String)} is used (DEEP search method).
|
|
||||||
* <p>
|
|
||||||
* If a {@link ReflectiveOperationException} occurs, this is printed to {@link System#out}
|
|
||||||
*
|
|
||||||
* @param instance The instance whose field value to set
|
|
||||||
* @param fieldName the name of the field
|
|
||||||
* @param newValue the new field value
|
|
||||||
* @throws IllegalArgumentException if the field doesn't exist
|
|
||||||
* @see #deepSearchField(Class, String)
|
|
||||||
* @see #setFieldValue(Class, String, Object, Object)
|
|
||||||
*/
|
|
||||||
public static void setFieldValue(Object instance, String fieldName, Object newValue) {
|
|
||||||
setFieldValue(deepSearchField(instance.getClass(), fieldName), instance, newValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets field value of the field named fieldName and the given instance
|
|
||||||
* To find the field, {@link #restrictedSearchField(Class, String)} is used (RESTRICTED search method).
|
|
||||||
* <p>
|
|
||||||
* If a {@link ReflectiveOperationException} occurs, this is printed to {@link System#out}
|
|
||||||
*
|
|
||||||
* @param clazz The class to search for the field
|
|
||||||
* @param fieldName the name of the field
|
|
||||||
* @param instance The field owner
|
|
||||||
* @param newValue The new field value
|
|
||||||
* @throws IllegalArgumentException if the field doesn't exist
|
|
||||||
* @see #restrictedSearchField(Class, String)
|
|
||||||
* @see #setFieldValue(Field, Object, Object)
|
|
||||||
*/
|
|
||||||
public static void setFieldValue(Class<?> clazz, String fieldName, Object instance, Object newValue) {
|
|
||||||
setFieldValue(restrictedSearchField(clazz, fieldName), instance, newValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets field value of the field named fieldName and the given instance
|
|
||||||
* To find the field, {@link #searchField(int, Class, String)} is used.
|
|
||||||
* <p>
|
|
||||||
* If a {@link ReflectiveOperationException} occurs, this is printed to {@link System#out}
|
|
||||||
*
|
|
||||||
* @param modifiers The modifiers for field search. Can have {@link #DEEP_SEARCH} and {@link #REMOVE_FINAL}
|
|
||||||
* @param clazz The class to search for the field
|
|
||||||
* @param instance The instance whose field value to set
|
|
||||||
* @param fieldName the name of the field
|
|
||||||
* @param newValue The new field value
|
|
||||||
* @throws IllegalArgumentException if the field doesn't exist
|
|
||||||
* @see #searchField(int, Class, String)
|
|
||||||
* @see #setFieldValue(Field, Object, Object)
|
|
||||||
*/
|
|
||||||
public static void setFieldValue(int modifiers, Class<?> clazz, String fieldName, Object instance, Object newValue) {
|
|
||||||
setFieldValue(searchField(modifiers, clazz, fieldName), instance, newValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a field value
|
|
||||||
* <p>
|
|
||||||
* If a {@link ReflectiveOperationException} occurs, this is printed to {@link System#out}
|
|
||||||
*
|
|
||||||
* @param field The field
|
|
||||||
* @param instance The field owner
|
|
||||||
* @param newValue The new field value
|
|
||||||
*/
|
|
||||||
public static void setFieldValue(Field field, Object instance, Object newValue) {
|
|
||||||
exceptionHandler.runSafe(() -> field.set(instance, newValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets static field value of the field name fieldName
|
|
||||||
* To find the field, {@link #restrictedSearchField(Class, String)} is used (RESTRICTED search method).
|
|
||||||
* <p>
|
|
||||||
* If a {@link ReflectiveOperationException} occurs, this is printed to {@link System#out}
|
|
||||||
*
|
|
||||||
* @param clazz The class to search for the field
|
|
||||||
* @param fieldName the name of the field
|
|
||||||
* @param newValue The new field value
|
|
||||||
* @throws IllegalArgumentException if the field doesn't exist
|
|
||||||
* @see #restrictedSearchField(Class, String)
|
|
||||||
* @see #setStaticFieldValue(Field, Object)
|
|
||||||
*/
|
|
||||||
public static void setStaticFieldValue(Class<?> clazz, String fieldName, Object newValue) {
|
|
||||||
setStaticFieldValue(restrictedSearchField(clazz, fieldName), newValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets static field value of the field named fieldName
|
|
||||||
* To find the field, {@link #searchField(int, Class, String)} is used.
|
|
||||||
* <p>
|
|
||||||
* If a {@link ReflectiveOperationException} occurs, this is printed to {@link System#out}
|
|
||||||
*
|
|
||||||
* @param modifiers The modifiers for field search. Can have {@link #DEEP_SEARCH} and {@link #REMOVE_FINAL}
|
|
||||||
* @param clazz The class to search for the field
|
|
||||||
* @param fieldName the name of the field
|
|
||||||
* @param newValue The new field value
|
|
||||||
* @throws IllegalArgumentException if the field doesn't exist
|
|
||||||
* @see #searchField(int, Class, String)
|
|
||||||
* @see #setStaticFieldValue(Field, Object)
|
|
||||||
*/
|
|
||||||
public static void setStaticFieldValue(int modifiers, Class<?> clazz, String fieldName, Object newValue) {
|
|
||||||
setStaticFieldValue(searchField(modifiers, clazz, fieldName), newValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a static field value
|
|
||||||
* <p>
|
|
||||||
* If a {@link ReflectiveOperationException} occurs, this is printed to {@link System#out}
|
|
||||||
*
|
|
||||||
* @param field The field
|
|
||||||
* @param newValue The new field value
|
|
||||||
*/
|
|
||||||
public static void setStaticFieldValue(Field field, Object newValue) {
|
|
||||||
setFieldValue(field, (Object) null, newValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
### METHOD METHODS ###
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Search a method of any accessibility within the class or any of its superclasses.
|
|
||||||
* The first method with the given name that is found will be returned.
|
|
||||||
* <p>
|
|
||||||
* If a method is found and it is not accessible, this method attempts to make it accessible.
|
|
||||||
* If a {@link SecurityException} is thrown in the process, that is ignored and the method will be returned nonetheless.
|
|
||||||
* <p>
|
|
||||||
* This method throws IllegalArgumentException if the Method is not found, because, in most cases, that should never happen,
|
|
||||||
* and it should simplify debugging. In some cases, if you want to know if the method exists, you'll have to use try/catch for that.
|
|
||||||
*
|
|
||||||
* @param clazz The lowest class in the ladder to start searching from
|
|
||||||
* @param methodName The name of the method
|
|
||||||
* @param parameterTypes the parameter types of the sought method.
|
|
||||||
* @return The method
|
|
||||||
* @throws NullPointerException if clazz is null or methodName is null
|
|
||||||
* @throws IllegalArgumentException if the method doesn't exist
|
|
||||||
* @see #restrictedSearchMethod(Class, String, Class[])
|
|
||||||
*/
|
|
||||||
public static Method deepSearchMethod(Class<?> clazz, String methodName, Class<?>... parameterTypes) {
|
|
||||||
return deepSearchMethod(0, clazz, methodName, parameterTypes);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Search a method of any accessibility within the class or any of its superclasses.
|
|
||||||
* The first method with the given name that is found will be returned.
|
|
||||||
* <p>
|
|
||||||
* If a method is found and it is not accessible, this method attempts to make it accessible.
|
|
||||||
* If a {@link SecurityException} is thrown in the process, that is ignored and the method will be returned nonetheless.
|
|
||||||
* <p>
|
|
||||||
* This method throws IllegalArgumentException if the Method is not found, because, in most cases, that should never happen,
|
|
||||||
* and it should simplify debugging. In some cases, if you want to know if the method exists, you'll have to use try/catch for that.
|
|
||||||
*
|
|
||||||
* @param modifiers The modifiers for method search. Can have {@link #IGNORE_PARAMS}
|
|
||||||
* @param clazz The lowest class in the ladder to start searching from
|
|
||||||
* @param methodName The name of the method
|
|
||||||
* @param parameterTypes the parameter types of the sought method.
|
|
||||||
* @return The method
|
|
||||||
* @throws NullPointerException if clazz is null or methodName is null
|
|
||||||
* @throws IllegalArgumentException if the method doesn't exist
|
|
||||||
* @see #restrictedSearchMethod(Class, String, Class[])
|
|
||||||
*/
|
|
||||||
public static Method deepSearchMethod(int modifiers, Class<?> clazz, String methodName, Class<?>... parameterTypes) {
|
|
||||||
Class<?> currentClass = clazz;
|
|
||||||
Method result;
|
|
||||||
do {
|
|
||||||
// throws NPE if class or methodName is null
|
|
||||||
result = internalSearchMethod(modifiers, currentClass, methodName, parameterTypes);
|
|
||||||
if (result != null) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
currentClass = currentClass.getSuperclass();
|
|
||||||
} while (currentClass != null);
|
|
||||||
|
|
||||||
throw new IllegalArgumentException("method not found in " + clazz.getCanonicalName() + " and superclasses: " + methodName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Search a method of any accessibility within the class, but not its superclasses.
|
|
||||||
* <p>
|
|
||||||
* If a method is found and it is not accessible, this method attempts to make it accessible.
|
|
||||||
* If a {@link SecurityException} is thrown in the process, that is ignored and the method will be returned nonetheless.
|
|
||||||
* <p>
|
|
||||||
* This method throws IllegalArgumentException if the Method is not found, because, in most cases, that should never happen,
|
|
||||||
* and it should simplify debugging. In some cases, if you want to know if the method exists, you'll have to use try/catch for that.
|
|
||||||
*
|
|
||||||
* @param clazz The only class to search for the method
|
|
||||||
* @param methodName The name of the method
|
|
||||||
* @param parameterTypes the parameter types of the sought method.
|
|
||||||
* @return The method
|
|
||||||
* @throws NullPointerException if clazz or methodName is null
|
|
||||||
* @throws IllegalArgumentException if the method does not exist
|
|
||||||
*/
|
|
||||||
public static Method restrictedSearchMethod(Class<?> clazz, String methodName, Class<?>... parameterTypes) {
|
|
||||||
return restrictedSearchMethod(0, clazz, methodName, parameterTypes);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Search a method of any accessibility within the class, but not its superclasses.
|
|
||||||
* <p>
|
|
||||||
* If a method is found and it is not accessible, this method attempts to make it accessible.
|
|
||||||
* If a {@link SecurityException} is thrown in the process, that is ignored and the method will be returned nonetheless.
|
|
||||||
* <p>
|
|
||||||
* This method throws IllegalArgumentException if the Method is not found, because, in most cases, that should never happen,
|
|
||||||
* and it should simplify debugging. In some cases, if you want to know if the method exists, you'll have to use try/catch for that.
|
|
||||||
*
|
|
||||||
* @param modifiers The modifiers for method search. Can have {@link #IGNORE_PARAMS}
|
|
||||||
* @param clazz The only class to search for the method
|
|
||||||
* @param methodName The name of the method
|
|
||||||
* @param parameterTypes the parameter types of the sought method.
|
|
||||||
* @return The method
|
|
||||||
* @throws NullPointerException if clazz or methodName is null
|
|
||||||
* @throws IllegalArgumentException if the method does not exist
|
|
||||||
*/
|
|
||||||
public static Method restrictedSearchMethod(int modifiers, Class<?> clazz, String methodName, Class<?>... parameterTypes) {
|
|
||||||
Method result = internalSearchMethod(modifiers, clazz, methodName, parameterTypes);
|
|
||||||
if (result == null) {
|
|
||||||
throw new IllegalArgumentException("method not found in " + clazz.getCanonicalName() + ": " + methodName);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Searches for a method using the given search method.
|
|
||||||
* <p>
|
|
||||||
* If a method is found and it is not accessible, this method attempts to make it accessible.
|
|
||||||
* If a {@link SecurityException} is thrown in the process, that is ignored and the method will be returned nonetheless.
|
|
||||||
* <p>
|
|
||||||
* This method throws IllegalArgumentException if the Method is not found, because, in most cases, that should never happen,
|
|
||||||
* and it should simplify debugging. In some cases, if you want to know if the method exists, you'll have to use try/catch for that.
|
|
||||||
*
|
|
||||||
* @param modifiers The modifiers for method search. Can have {@link #DEEP_SEARCH} and {@link #IGNORE_PARAMS}
|
|
||||||
* @param clazz The class to search in/from
|
|
||||||
* @param methodName Name of the method
|
|
||||||
* @param parameterTypes the parameter types of the sought method.
|
|
||||||
* @return The method
|
|
||||||
* @throws NullPointerException if clazz or methodName is null
|
|
||||||
* @throws IllegalArgumentException if the method is not found
|
|
||||||
*/
|
|
||||||
public static Method searchMethod(int modifiers, Class<?> clazz, String methodName, Class<?>... parameterTypes) {
|
|
||||||
if ((modifiers & DEEP_SEARCH) != 0) {
|
|
||||||
return deepSearchMethod(modifiers, clazz, methodName, parameterTypes);
|
|
||||||
} else {
|
|
||||||
return restrictedSearchMethod(modifiers, clazz, methodName, parameterTypes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The same as {@link #restrictedSearchMethod(Class, String, Class[]) }, but returns null instead of throwing IllegalArgumentException
|
|
||||||
* @see #restrictedSearchMethod(Class, String, Class[])
|
|
||||||
*/
|
|
||||||
private static Method internalSearchMethod(int modifiers, Class<?> clazz, String methodName, Class<?>... parameterTypes) {
|
|
||||||
Method result = null;
|
|
||||||
|
|
||||||
if ((modifiers & IGNORE_PARAMS) != 0) {
|
|
||||||
|
|
||||||
// throws NullPointerException if either clazz or methodName are null.
|
|
||||||
methodName = methodName.intern();
|
|
||||||
for (Method method : clazz.getDeclaredMethods()) {
|
|
||||||
// all method names are interned. Identity comparison is much faster.
|
|
||||||
if (method.getName() == methodName) {
|
|
||||||
result = method;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
try {
|
|
||||||
// throws NullPointerException if either clazz or methodName are null.
|
|
||||||
result = clazz.getDeclaredMethod(methodName, parameterTypes);
|
|
||||||
} catch (NoSuchMethodException | SecurityException ex) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!result.isAccessible()) try {
|
|
||||||
result.setAccessible(true);
|
|
||||||
} catch (SecurityException ignored) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invokes the method named methodName with the given instance and arguments
|
|
||||||
* To find the method, {@link #searchMethod(int, Class, String, Class[])} is used with no type parameters,
|
|
||||||
* modifiers {@link #DEEP_SEARCH} and {@link #IGNORE_PARAMS}, and the class {@link Object#getClass() instance.getClass()}
|
|
||||||
* <p>
|
|
||||||
* To search the method with type parameters, you should search the method using {@link #searchMethod(int, Class, String, Class[])} or similar,
|
|
||||||
* and call {@link #invokeMethod(Method, Object, Object...)}
|
|
||||||
* <p>
|
|
||||||
* If a {@link ReflectiveOperationException} occurs, this is printed to {@link System#out}
|
|
||||||
*
|
|
||||||
* @param methodName Name of the method
|
|
||||||
* @param instance The instance to invoke the method on
|
|
||||||
* @param args The arguments to use in the method call
|
|
||||||
* @param <T> The expected/known method return type
|
|
||||||
* @return The result of calling the method
|
|
||||||
* @throws NullPointerException if instance or methodName is null
|
|
||||||
* @throws IllegalArgumentException if the method is not found
|
|
||||||
* @see #invokeMethod(Method, Object, Object...)
|
|
||||||
*/
|
|
||||||
public static <T> T invokeMethod(Object instance, String methodName, Object... args) {
|
|
||||||
return invokeMethod(searchMethod(DEEP_SEARCH | IGNORE_PARAMS, instance.getClass(), methodName), instance, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invokes the method named methodName with the given instance and arguments
|
|
||||||
* To find the method, {@link #searchMethod(int, Class, String, Class[])} is used with no type parameters,
|
|
||||||
* as well as the modifier {@link #IGNORE_PARAMS}
|
|
||||||
* <p>
|
|
||||||
* To search the method with type parameters, you should search the method using {@link #searchMethod(int, Class, String, Class[])} or similar,
|
|
||||||
* and call {@link #invokeMethod(Method, Object, Object...)}
|
|
||||||
* <p>
|
|
||||||
* If a {@link ReflectiveOperationException} occurs, this is printed to {@link System#out}
|
|
||||||
*
|
|
||||||
* @param clazz The class to search in/from
|
|
||||||
* @param methodName Name of the method
|
|
||||||
* @param instance The instance to invoke the method on
|
|
||||||
* @param args The arguments to use in the method call
|
|
||||||
* @param <T> The expected/known method return type
|
|
||||||
* @return The result of calling the method
|
|
||||||
* @throws NullPointerException if clazz or methodName is null
|
|
||||||
* @throws IllegalArgumentException if the method is not found
|
|
||||||
* @see #invokeMethod(Method, Object, Object...)
|
|
||||||
*/
|
|
||||||
public static <T> T invokeMethod(Class<?> clazz, String methodName, Object instance, Object... args) {
|
|
||||||
return invokeMethod(searchMethod(IGNORE_PARAMS, clazz, methodName), instance, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invokes the method named methodName with the given instance and arguments
|
|
||||||
* To find the method, {@link #searchMethod(int, Class, String, Class[])} is used with no type parameters.
|
|
||||||
* For this search, the result of calling {@link Object#getClass() instance.getClass()} is used.
|
|
||||||
* <p>
|
|
||||||
* To search the method with type parameters, you should search the method using {@link #searchMethod(int, Class, String, Class[])} or similar,
|
|
||||||
* and call {@link #invokeMethod(Method, Object, Object...)}
|
|
||||||
* <p>
|
|
||||||
* If a {@link ReflectiveOperationException} occurs, this is printed to {@link System#out}
|
|
||||||
*
|
|
||||||
* @param modifiers The modifiers for method search. Can have {@link #DEEP_SEARCH} and {@link #IGNORE_PARAMS}
|
|
||||||
* @param methodName Name of the method
|
|
||||||
* @param instance The instance to invoke the method on
|
|
||||||
* @param args The arguments to use in the method call
|
|
||||||
* @param <T> The expected/known method return type
|
|
||||||
* @return The result of calling the method
|
|
||||||
* @throws NullPointerException if instance or methodName is null
|
|
||||||
* @throws IllegalArgumentException if the method is not found
|
|
||||||
* @see #invokeMethod(Method, Object, Object...)
|
|
||||||
*/
|
|
||||||
public static <T> T invokeMethod(int modifiers, Object instance, String methodName, Object... args) {
|
|
||||||
return invokeMethod(searchMethod(modifiers, instance.getClass(), methodName), instance, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invokes the method named methodName with the given instance and arguments
|
|
||||||
* To find the method, {@link #searchMethod(int, Class, String, Class[])} is used with no type parameters.
|
|
||||||
* <p>
|
|
||||||
* To search the method with type parameters, you should search the method using {@link #searchMethod(int, Class, String, Class[])} or similar,
|
|
||||||
* and call {@link #invokeMethod(Method, Object, Object...)}
|
|
||||||
* <p>
|
|
||||||
* If a {@link ReflectiveOperationException} occurs, this is printed to {@link System#out}
|
|
||||||
*
|
|
||||||
* @param modifiers The modifiers for method search. Can have {@link #DEEP_SEARCH} and {@link #IGNORE_PARAMS}
|
|
||||||
* @param clazz The class to search in/from
|
|
||||||
* @param methodName Name of the method
|
|
||||||
* @param instance The instance to invoke the method on
|
|
||||||
* @param args The arguments to use in the method call
|
|
||||||
* @param <T> The expected/known method return type
|
|
||||||
* @return The result of calling the method
|
|
||||||
* @throws NullPointerException if clazz or methodName is null
|
|
||||||
* @throws IllegalArgumentException if the method is not found
|
|
||||||
* @see #invokeMethod(Method, Object, Object...)
|
|
||||||
*/
|
|
||||||
public static <T> T invokeMethod(int modifiers, Class<?> clazz, String methodName, Object instance, Object... args) {
|
|
||||||
return invokeMethod(searchMethod(modifiers, clazz, methodName), instance, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invokes the method with the given instance and arguments
|
|
||||||
* <p>
|
|
||||||
* If a {@link ReflectiveOperationException} occurs, this is printed to {@link System#out}
|
|
||||||
*
|
|
||||||
* @param method The method to invoke
|
|
||||||
* @param instance The instance to invoke the method on
|
|
||||||
* @param args The arguments to use in the method call
|
|
||||||
* @param <T> The expected/known method return type
|
|
||||||
* @return The result of calling the method
|
|
||||||
*/
|
|
||||||
public static <T> T invokeMethod(Method method, Object instance, Object... args) {
|
|
||||||
return exceptionHandler.supplySafe(() -> (T) method.invoke(instance, args));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invokes the static method named methodName with the given arguments
|
|
||||||
* To find the method, {@link #searchMethod(int, Class, String, Class[])} is used with no type parameters,
|
|
||||||
* as well as the modifier {@link #IGNORE_PARAMS}
|
|
||||||
* <p>
|
|
||||||
* To search the method with type parameters, you should search the method using {@link #searchMethod(int, Class, String, Class[])} or similar,
|
|
||||||
* and call {@link #invokeMethod(Method, Object, Object...)}
|
|
||||||
* <p>
|
|
||||||
* If a {@link ReflectiveOperationException} occurs, this is printed to {@link System#out}
|
|
||||||
*
|
|
||||||
* @param clazz The class to search in/from
|
|
||||||
* @param methodName Name of the method
|
|
||||||
* @param args The arguments to use in the method call
|
|
||||||
* @param <T> The expected/known method return type
|
|
||||||
* @return The result of calling the method
|
|
||||||
* @throws NullPointerException if clazz or methodName is null
|
|
||||||
* @throws IllegalArgumentException if the method is not found
|
|
||||||
* @see #invokeStaticMethod(Method, Object...)
|
|
||||||
*/
|
|
||||||
public static <T> T invokeStaticMethod(Class<?> clazz, String methodName, Object... args) {
|
|
||||||
return invokeStaticMethod(searchMethod(IGNORE_PARAMS, clazz, methodName), args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invokes the static method named methodName with the given arguments
|
|
||||||
* To find the method, {@link #searchMethod(int, Class, String, Class[])} is used with no type parameters.
|
|
||||||
* <p>
|
|
||||||
* To search the method with type parameters, you should search the method using {@link #searchMethod(int, Class, String, Class[])} or similar,
|
|
||||||
* and call {@link #invokeMethod(Method, Object, Object...)}
|
|
||||||
* <p>
|
|
||||||
* If a {@link ReflectiveOperationException} occurs, this is printed to {@link System#out}
|
|
||||||
*
|
|
||||||
* @param modifiers The modifiers for method search. Can have {@link #DEEP_SEARCH} and {@link #IGNORE_PARAMS}
|
|
||||||
* @param clazz The class to search in/from
|
|
||||||
* @param methodName Name of the method
|
|
||||||
* @param args The arguments to use in the method call
|
|
||||||
* @param <T> The expected/known method return type
|
|
||||||
* @return The result of calling the method
|
|
||||||
* @throws NullPointerException if clazz or methodName is null
|
|
||||||
* @throws IllegalArgumentException if the method is not found
|
|
||||||
* @see #invokeStaticMethod(Method, Object...)
|
|
||||||
*/
|
|
||||||
public static <T> T invokeStaticMethod(int modifiers, Class<?> clazz, String methodName, Object... args) {
|
|
||||||
return invokeStaticMethod(searchMethod(modifiers, clazz, methodName), args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invokes the static method with the given arguments
|
|
||||||
* <p>
|
|
||||||
* If a {@link ReflectiveOperationException} occurs, this is printed to {@link System#out}
|
|
||||||
*
|
|
||||||
* @param method The method to invoke
|
|
||||||
* @param args The arguments to use in the method call
|
|
||||||
* @param <T> The expected/known method return type
|
|
||||||
* @return The result of calling the method
|
|
||||||
* @see #invokeMethod(Method, Object, Object...)
|
|
||||||
*/
|
|
||||||
public static <T> T invokeStaticMethod(Method method, Object... args) {
|
|
||||||
return invokeMethod(method, (Object) null, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,987 +0,0 @@
|
|||||||
package io.dico.dicore;
|
|
||||||
|
|
||||||
import org.bukkit.Server;
|
|
||||||
import org.bukkit.event.*;
|
|
||||||
import org.bukkit.event.player.PlayerEvent;
|
|
||||||
import org.bukkit.event.player.PlayerKickEvent;
|
|
||||||
import org.bukkit.event.player.PlayerQuitEvent;
|
|
||||||
import org.bukkit.event.server.PluginDisableEvent;
|
|
||||||
import org.bukkit.event.server.PluginEnableEvent;
|
|
||||||
import org.bukkit.plugin.EventExecutor;
|
|
||||||
import org.bukkit.plugin.Plugin;
|
|
||||||
import org.bukkit.plugin.RegisteredListener;
|
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
import java.lang.reflect.*;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class acts as a utility to register event listeners in a functional manner.
|
|
||||||
* Listeners passed are always {@code <T> Consumer<T extends Event>} objects.
|
|
||||||
* <p>
|
|
||||||
* Registrations are made using its
|
|
||||||
* * {@link #registerListener(Class, Consumer)}
|
|
||||||
* * {@link #registerListener(Class, EventPriority, Consumer)}
|
|
||||||
* * {@link #registerListener(Class, boolean, Consumer)}
|
|
||||||
* * {@link #registerListener(Class, EventPriority, boolean, Consumer)}
|
|
||||||
* * {@link #registerListeners(Class)}
|
|
||||||
* * {@link #registerListeners(Object)}
|
|
||||||
* * {@link #registerListeners(Class, Object)}
|
|
||||||
* * {@link #registerPlayerQuitListener(Consumer)}
|
|
||||||
* methods.
|
|
||||||
* <p>
|
|
||||||
* Listeners registered in this way are generally a bit faster than when registered through {@link org.bukkit.plugin.PluginManager#registerEvents(Listener, Plugin)}
|
|
||||||
* Because it does not use reflection to call the event handlers.
|
|
||||||
*
|
|
||||||
* @implNote This class uses only one {@link Listener listener object} across all its instances, by fooling spigot into
|
|
||||||
* thinking they're all distinct ones (by violating the {@link Object#equals(Object)} contract).
|
|
||||||
* <p>
|
|
||||||
* Standard Registrator instances also use a fake plugin identity to register its listeners.
|
|
||||||
* You can use the {{@link #Registrator(Plugin)}} constructor to use real plugin identities.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("DanglingJavadoc")
|
|
||||||
public final class Registrator {
|
|
||||||
|
|
||||||
// ############################################
|
|
||||||
// # Public static methods
|
|
||||||
// ############################################
|
|
||||||
|
|
||||||
public static Registrator getInstance() {
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ############################################
|
|
||||||
// # Static fields and initializer
|
|
||||||
// ############################################
|
|
||||||
|
|
||||||
private static final Registrator instance;
|
|
||||||
private static final Listener universalListenerObject;
|
|
||||||
private static final Plugin defaultFakePlugin;
|
|
||||||
private static final Map<Class<?>, HandlerListInfo> handlerListCache;
|
|
||||||
|
|
||||||
static {
|
|
||||||
handlerListCache = new IdentityHashMap<>();
|
|
||||||
defaultFakePlugin = new RegistratorPlugin();
|
|
||||||
instance = new Registrator();
|
|
||||||
universalListenerObject = new Listener() {
|
|
||||||
|
|
||||||
//@formatter:off
|
|
||||||
/** return false here to fool the HandlerList into believing each registration is from another Listener.
|
|
||||||
* as a result, no exceptions will be thrown when registering multiple listeners for the same event and priority.
|
|
||||||
*
|
|
||||||
* Another option is to have this for each instance:
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* <pre>{@code
|
|
||||||
private Listener getListenerFor(HandlerList list, EventPriority priority) {
|
|
||||||
int needed = (int) (listeners.get(list).stream().filter(listener -> listener.getPriority() == priority).count() + 1);
|
|
||||||
while (needed > myListeners.size()) {
|
|
||||||
myListeners.add(new Listener() {});
|
|
||||||
}
|
|
||||||
return myListeners.get(needed - 1);
|
|
||||||
}
|
|
||||||
* }</pre>
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* Where {@code myListeners} is a List<Listener>
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
//@formatter:on
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// ############################################
|
|
||||||
// # Instance fields and constructors
|
|
||||||
// ############################################
|
|
||||||
|
|
||||||
private final List<Registration> registrations;
|
|
||||||
private Plugin plugin;
|
|
||||||
private Registration pluginEnableListener;
|
|
||||||
private Registration pluginDisableListener;
|
|
||||||
private boolean enabled;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new instance using the {@link #defaultFakePlugin universal plugin object}
|
|
||||||
*/
|
|
||||||
public Registrator() {
|
|
||||||
this(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new instance using an artificial plugin.
|
|
||||||
*
|
|
||||||
* @param distinctPlugin true if the artificial plugin should be distinct from the {@link #defaultFakePlugin universal plugin object}
|
|
||||||
*/
|
|
||||||
public Registrator(boolean distinctPlugin) {
|
|
||||||
this(distinctPlugin ? new RegistratorPlugin() : defaultFakePlugin);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new instance using the given plugin
|
|
||||||
*
|
|
||||||
* @param plugin The plugin to register the listeners with
|
|
||||||
* @throws NullPointerException if plugin is null
|
|
||||||
*/
|
|
||||||
public Registrator(Plugin plugin) {
|
|
||||||
this.registrations = new ArrayList<>();
|
|
||||||
setPlugin(plugin);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ############################################
|
|
||||||
// # Internal static methods
|
|
||||||
// ############################################
|
|
||||||
|
|
||||||
/**
|
|
||||||
* static {@link EventExecutor} instantiator to make sure executors don't reference any objects unnecessarily.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private static <T extends Event> EventExecutor newEventExecutor(Class<T> eventClass, Consumer<? super T> handler) {
|
|
||||||
if (getHandlerListInfoOf(eventClass).requiresFilter) {
|
|
||||||
return (ignored, event) -> {
|
|
||||||
if (eventClass.isInstance(event)) {
|
|
||||||
handler.accept((T) event);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return (ignored, event) -> handler.accept((T) event);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reflectively acquire the HandlerList for the given event type.
|
|
||||||
*
|
|
||||||
* @param eventClass the Event type
|
|
||||||
* @return its HandlerList, or null if one can't be found
|
|
||||||
*/
|
|
||||||
private static HandlerList getHandlerListOf(Class<?> eventClass) {
|
|
||||||
try {
|
|
||||||
return getHandlerListInfoOf(eventClass).handlerList;
|
|
||||||
} catch (RuntimeException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static HandlerListInfo getHandlerListInfoOf(Class<?> eventClass) {
|
|
||||||
return handlerListCache.computeIfAbsent(eventClass, clz -> {
|
|
||||||
Method method = Reflection.deepSearchMethod(clz, "getHandlerList");
|
|
||||||
boolean requiresFilter = clz != method.getDeclaringClass();
|
|
||||||
return new HandlerListInfo(Reflection.invokeStaticMethod(method), requiresFilter);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ############################################
|
|
||||||
// # Public instance methods
|
|
||||||
// ############################################
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Change the plugin used by the listeners of this registrator.
|
|
||||||
*
|
|
||||||
* @param plugin the plugin to use
|
|
||||||
* @throws NullPointerException if plugin is null
|
|
||||||
* @throws IllegalStateException if this registrator was returned by {@link #getInstance()}
|
|
||||||
*/
|
|
||||||
public void setPlugin(Plugin plugin) {
|
|
||||||
Objects.requireNonNull(plugin);
|
|
||||||
if (this.plugin == plugin) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.plugin != null) {
|
|
||||||
if (this == instance) {
|
|
||||||
throw new IllegalStateException("You may not modify the plugin used by the universal Registrator instance");
|
|
||||||
}
|
|
||||||
|
|
||||||
setEnabled(false);
|
|
||||||
setPluginListenerRegisteredStates(false, false);
|
|
||||||
setListenersPluginTo(plugin);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.plugin = plugin;
|
|
||||||
initPluginListeners();
|
|
||||||
updatePluginListeners(plugin.isEnabled());
|
|
||||||
if (plugin.isEnabled()) {
|
|
||||||
setEnabled(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The plugin object used when registering the listeners
|
|
||||||
*/
|
|
||||||
public Plugin getRegistrationPlugin() {
|
|
||||||
return plugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return True if the plugin used was artificial / not an actual plugin on the server / fooled the bukkit api
|
|
||||||
*/
|
|
||||||
public boolean hasFakePlugin() {
|
|
||||||
return plugin instanceof RegistratorPlugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return An unmodifiable view of the registrations made by this {@link Registrator}
|
|
||||||
*/
|
|
||||||
public List<Registration> getListeners() {
|
|
||||||
return Collections.unmodifiableList(registrations);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Make a new listener handle for the given event type.
|
|
||||||
* The returned listener handle is not managed by this {@link Registrator}, and you must register it yourself.
|
|
||||||
* the event priority is set to {@link EventPriority#HIGHEST}
|
|
||||||
* the ignore cancelled flag is set to {@code true}
|
|
||||||
*
|
|
||||||
* @param eventClass The event type
|
|
||||||
* @param handler the listener
|
|
||||||
* @param <T> the event type
|
|
||||||
* @return this
|
|
||||||
* /
|
|
||||||
public <T extends Event> ListenerHandle makeListenerHandle(Class<T> eventClass, Consumer<? super T> handler) {
|
|
||||||
return makeListenerHandle(eventClass, EventPriority.HIGHEST, handler);
|
|
||||||
}/* */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Make a new listener handle for the given event type.
|
|
||||||
* The returned listener handle is not managed by this {@link Registrator}, and you must register it yourself.
|
|
||||||
* The ignoreCancelled flag is set to false if {@code priority} is {@link EventPriority#LOW} or {@link EventPriority#LOWEST}
|
|
||||||
* otherwise, it is set to true.
|
|
||||||
*
|
|
||||||
* @param eventClass The event type
|
|
||||||
* @param priority the event priority
|
|
||||||
* @param handler the listener
|
|
||||||
* @param <T> the event type
|
|
||||||
* @return this
|
|
||||||
* /
|
|
||||||
public <T extends Event> ListenerHandle makeListenerHandle(Class<T> eventClass, EventPriority priority, Consumer<? super T> handler) {
|
|
||||||
boolean ignoreCancelled = Cancellable.class.isAssignableFrom(eventClass) && priority.getSlot() > EventPriority.LOW.getSlot();
|
|
||||||
return makeListenerHandle(eventClass, priority, ignoreCancelled, handler);
|
|
||||||
}/* */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Make a new listener handle for the given event type.
|
|
||||||
* The returned listener handle is not managed by this {@link Registrator}, and you must register it yourself.
|
|
||||||
* If {@code ignoreCancelled} is true, the event priority is set to {@link EventPriority#HIGHEST}
|
|
||||||
* Otherwise, it is set to {@link EventPriority#LOW}
|
|
||||||
*
|
|
||||||
* @param eventClass The event type
|
|
||||||
* @param ignoreCancelled the ignoreCancelled flag of the listener
|
|
||||||
* @param handler The listener
|
|
||||||
* @param <T> The event type
|
|
||||||
* @return this
|
|
||||||
* /
|
|
||||||
public <T extends Event> ListenerHandle makeListenerHandle(Class<T> eventClass, boolean ignoreCancelled, Consumer<? super T> handler) {
|
|
||||||
return makeListenerHandle(eventClass, ignoreCancelled ? EventPriority.HIGHEST : EventPriority.LOW, ignoreCancelled, handler);
|
|
||||||
}/* */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Make a new listener handle for the given event type.
|
|
||||||
* The returned listener handle is not managed by this {@link Registrator}, and you must register it yourself.
|
|
||||||
*
|
|
||||||
* @param eventClass The event type
|
|
||||||
* @param priority the event priority
|
|
||||||
* @param ignoreCancelled the ignoreCancelled flag of the listener
|
|
||||||
* @param handler the listener
|
|
||||||
* @param <T> the event type
|
|
||||||
* @return this
|
|
||||||
* /
|
|
||||||
public <T extends Event> ListenerHandle makeListenerHandle(Class<T> eventClass, EventPriority priority, boolean ignoreCancelled, Consumer<? super T> handler) {
|
|
||||||
return (ListenerHandle) createRegistration(true, priority, ignoreCancelled, eventClass, handler);
|
|
||||||
}/* */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register a listener for the given event type.
|
|
||||||
* the event priority is set to {@link EventPriority#HIGHEST}
|
|
||||||
* the ignore cancelled flag is set to {@code true}
|
|
||||||
*
|
|
||||||
* @param eventClass The event type
|
|
||||||
* @param handler the listener
|
|
||||||
* @param <T> the event type
|
|
||||||
* @return this
|
|
||||||
*/
|
|
||||||
public <T extends Event> Registrator registerListener(Class<T> eventClass, Consumer<? super T> handler) {
|
|
||||||
return registerListener(eventClass, EventPriority.HIGHEST, handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register a listener for the given event type.
|
|
||||||
* The ignoreCancelled flag is set to false if {@code priority} is {@link EventPriority#LOW} or {@link EventPriority#LOWEST}
|
|
||||||
* otherwise, it is set to true.
|
|
||||||
*
|
|
||||||
* @param eventClass The event type
|
|
||||||
* @param priority the event priority
|
|
||||||
* @param handler the listener
|
|
||||||
* @param <T> the event type
|
|
||||||
* @return this
|
|
||||||
*/
|
|
||||||
public <T extends Event> Registrator registerListener(Class<T> eventClass, EventPriority priority, Consumer<? super T> handler) {
|
|
||||||
boolean ignoreCancelled = Cancellable.class.isAssignableFrom(eventClass) && priority.getSlot() > EventPriority.LOW.getSlot();
|
|
||||||
return registerListener(eventClass, priority, ignoreCancelled, handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register a listener for the given event type.
|
|
||||||
* If {@code ignoreCancelled} is true, the event priority is set to {@link EventPriority#HIGHEST}
|
|
||||||
* Otherwise, it is set to {@link EventPriority#LOW}
|
|
||||||
*
|
|
||||||
* @param eventClass The event type
|
|
||||||
* @param ignoreCancelled the ignoreCancelled flag of the listener
|
|
||||||
* @param handler The listener
|
|
||||||
* @param <T> The event type
|
|
||||||
* @return this
|
|
||||||
*/
|
|
||||||
public <T extends Event> Registrator registerListener(Class<T> eventClass, boolean ignoreCancelled, Consumer<? super T> handler) {
|
|
||||||
return registerListener(eventClass, ignoreCancelled ? EventPriority.HIGHEST : EventPriority.LOW, ignoreCancelled, handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register a listener for the given event type.
|
|
||||||
*
|
|
||||||
* @param eventClass The event type
|
|
||||||
* @param priority the event priority
|
|
||||||
* @param ignoreCancelled the ignoreCancelled flag of the listener
|
|
||||||
* @param handler the listener
|
|
||||||
* @param <T> the event type
|
|
||||||
* @return this
|
|
||||||
*/
|
|
||||||
public <T extends Event> Registrator registerListener(Class<T> eventClass, EventPriority priority, boolean ignoreCancelled, Consumer<? super T> handler) {
|
|
||||||
registerListener(createRegistration(false, priority, ignoreCancelled, eventClass, handler));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Registrator registerListeners(Class<?> clazz, Object instance) {
|
|
||||||
for (ListenerFieldInfo fieldInfo : getListenerFields(clazz, instance)) {
|
|
||||||
registerListener(fieldInfo.eventClass, fieldInfo.anno.priority(), fieldInfo.anno.ignoreCancelled(), fieldInfo.lambda);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Registrator registerListeners(Class<?> clazz) {
|
|
||||||
return registerListeners(clazz, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Registrator registerListeners(Object instance) {
|
|
||||||
return registerListeners(instance.getClass(), instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* * /
|
|
||||||
public ChainedListenerHandle makeChainedListenerHandle(Class<?> clazz, Object instance) {
|
|
||||||
ChainedListenerHandle rv = ChainedListenerHandles.empty();
|
|
||||||
for (ListenerFieldInfo fieldInfo : getListenerFields(clazz, instance)) {
|
|
||||||
rv = rv.withElement(makeListenerHandle(fieldInfo.eventClass, fieldInfo.anno.priority(), fieldInfo.anno.ignoreCancelled(), fieldInfo.lambda));
|
|
||||||
}
|
|
||||||
return rv;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ChainedListenerHandle makeChainedListenerHandle(Class<?> clazz) {
|
|
||||||
return makeChainedListenerHandle(clazz, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ChainedListenerHandle makeChainedListenerHandle(Object instance) {
|
|
||||||
return makeChainedListenerHandle(instance.getClass(), instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ListenerHandle makePlayerQuitListenerHandle(Consumer<? super PlayerEvent> handler) {
|
|
||||||
ListenerHandle first = makeListenerHandle(PlayerQuitEvent.class, EventPriority.NORMAL, handler);
|
|
||||||
ListenerHandle second = makeListenerHandle(PlayerKickEvent.class, EventPriority.NORMAL, handler);
|
|
||||||
return ChainedListenerHandles.singleton(first).withElement(second);
|
|
||||||
}
|
|
||||||
/* */
|
|
||||||
|
|
||||||
public Registrator registerPlayerQuitListener(Consumer<? super PlayerEvent> handler) {
|
|
||||||
registerListener(PlayerQuitEvent.class, EventPriority.NORMAL, handler);
|
|
||||||
return registerListener(PlayerKickEvent.class, EventPriority.NORMAL, handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "Registrator{" +
|
|
||||||
"plugin: " + plugin +
|
|
||||||
", enabled: " + enabled +
|
|
||||||
", registrations: " + registrations.size() +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toStringWithAllRegistrations() {
|
|
||||||
StringBuilder sb = new StringBuilder("Registrator {");
|
|
||||||
sb.append("\n plugin: ").append(plugin);
|
|
||||||
sb.append("\n enabled: ").append(enabled);
|
|
||||||
sb.append("\n registrations: [");
|
|
||||||
|
|
||||||
Iterator<Registration> iterator = registrations.iterator();
|
|
||||||
if (iterator.hasNext()) {
|
|
||||||
sb.append("\n ").append(iterator.next().toString());
|
|
||||||
}
|
|
||||||
while (iterator.hasNext()) {
|
|
||||||
sb.append(',').append("\n ").append(iterator.next().toString());
|
|
||||||
}
|
|
||||||
if (!registrations.isEmpty()) {
|
|
||||||
sb.append("\n ");
|
|
||||||
}
|
|
||||||
sb.append("]\n}");
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ############################################
|
|
||||||
// # Public types
|
|
||||||
// ############################################
|
|
||||||
|
|
||||||
public interface IEventListener<T extends Event> extends Consumer<T> {
|
|
||||||
@Override
|
|
||||||
void accept(T event);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
@Target(ElementType.FIELD)
|
|
||||||
public @interface ListenerInfo {
|
|
||||||
|
|
||||||
String[] events() default {};
|
|
||||||
|
|
||||||
EventPriority priority() default EventPriority.HIGHEST;
|
|
||||||
|
|
||||||
boolean ignoreCancelled() default true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Registration extends RegisteredListener {
|
|
||||||
|
|
||||||
private final EventExecutor executor;
|
|
||||||
private final Class<?> eventClass;
|
|
||||||
private final StackTraceElement caller;
|
|
||||||
private boolean registered;
|
|
||||||
|
|
||||||
Registration(Class<?> eventClass, StackTraceElement caller, EventExecutor executor, EventPriority priority, Plugin plugin, boolean ignoreCancelled) {
|
|
||||||
super(universalListenerObject, executor, priority, plugin, ignoreCancelled);
|
|
||||||
this.executor = executor;
|
|
||||||
this.eventClass = eventClass;
|
|
||||||
this.caller = caller;
|
|
||||||
}
|
|
||||||
|
|
||||||
Registration setPlugin(Plugin plugin) {
|
|
||||||
if (getPlugin() == plugin) {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
boolean registered = this.registered;
|
|
||||||
unregister();
|
|
||||||
Registration out = new Registration(eventClass, caller, executor, getPriority(), plugin, isIgnoringCancelled());
|
|
||||||
if (registered) {
|
|
||||||
out.register();
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Class<?> getEventClass() {
|
|
||||||
return eventClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
public StackTraceElement getCaller() {
|
|
||||||
return caller;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isRegistered() {
|
|
||||||
return registered;
|
|
||||||
}
|
|
||||||
|
|
||||||
void register() {
|
|
||||||
if (!registered) {
|
|
||||||
registered = true;
|
|
||||||
getHandlerListOf(eventClass).register(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void unregister() {
|
|
||||||
if (registered) {
|
|
||||||
registered = false;
|
|
||||||
getHandlerListOf(eventClass).unregister(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "Listener for " + eventClass.getSimpleName() + (caller == null ? "" : " registered at " + caller.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// ############################################
|
|
||||||
// # Internal instance methods
|
|
||||||
// ############################################
|
|
||||||
|
|
||||||
@SuppressWarnings("UnusedReturnValue")
|
|
||||||
private boolean setEnabled(boolean enabled) {
|
|
||||||
if (this.enabled != enabled) {
|
|
||||||
this.enabled = enabled;
|
|
||||||
if (enabled) {
|
|
||||||
registerAllListeners();
|
|
||||||
} else {
|
|
||||||
unregisterAllListeners();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initPluginListeners() {
|
|
||||||
if (hasFakePlugin()) {
|
|
||||||
pluginEnableListener = pluginDisableListener = null;
|
|
||||||
} else {
|
|
||||||
if (pluginEnableListener != null) {
|
|
||||||
pluginEnableListener = pluginEnableListener.setPlugin(plugin);
|
|
||||||
} else {
|
|
||||||
pluginEnableListener = createRegistration(null, false, EventPriority.NORMAL, false, PluginEnableEvent.class, this::onPluginEnable);
|
|
||||||
}
|
|
||||||
if (pluginDisableListener != null) {
|
|
||||||
pluginDisableListener = pluginDisableListener.setPlugin(plugin);
|
|
||||||
} else {
|
|
||||||
pluginDisableListener = createRegistration(null, false, EventPriority.NORMAL, false, PluginDisableEvent.class, this::onPluginDisable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updatePluginListeners(boolean pluginEnabled) {
|
|
||||||
if (hasFakePlugin()) {
|
|
||||||
setPluginListenerRegisteredStates(false, false);
|
|
||||||
} else {
|
|
||||||
setPluginListenerRegisteredStates(!pluginEnabled, pluginEnabled);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setPluginListenerRegisteredStates(boolean enableListenerRegistered, boolean disableListenerRegistered) {
|
|
||||||
if (pluginEnableListener != null) {
|
|
||||||
if (enableListenerRegistered) {
|
|
||||||
PluginEnableEvent.getHandlerList().register(pluginEnableListener);
|
|
||||||
} else {
|
|
||||||
PluginEnableEvent.getHandlerList().unregister(pluginEnableListener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (pluginDisableListener != null) {
|
|
||||||
if (disableListenerRegistered) {
|
|
||||||
PluginDisableEvent.getHandlerList().register(pluginDisableListener);
|
|
||||||
} else {
|
|
||||||
PluginDisableEvent.getHandlerList().unregister(pluginDisableListener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onPluginEnable(PluginEnableEvent event) {
|
|
||||||
if (event.getPlugin() == plugin) {
|
|
||||||
setEnabled(true);
|
|
||||||
updatePluginListeners(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onPluginDisable(PluginDisableEvent event) {
|
|
||||||
if (event.getPlugin() == plugin) {
|
|
||||||
setEnabled(false);
|
|
||||||
updatePluginListeners(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setListenersPluginTo(Plugin plugin) {
|
|
||||||
List<Registration> registrations = this.registrations;
|
|
||||||
for (int n = registrations.size(), i = 0; i < n; i++) {
|
|
||||||
registrations.set(i, registrations.get(i).setPlugin(plugin));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void registerListener(Registration registration) {
|
|
||||||
registrations.add(registration);
|
|
||||||
if (enabled) {
|
|
||||||
registration.register();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private <T extends Event> Registration createRegistration(boolean asHandle,
|
|
||||||
EventPriority priority,
|
|
||||||
boolean ignoreCancelled,
|
|
||||||
Class<T> eventClass,
|
|
||||||
Consumer<? super T> handler) {
|
|
||||||
StackTraceElement caller = null;
|
|
||||||
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
|
||||||
if (stackTrace.length > 0) {
|
|
||||||
String className = Registrator.class.getName();
|
|
||||||
for (StackTraceElement element : stackTrace) {
|
|
||||||
if (!element.getClassName().equals(className) && !element.getClassName().startsWith("java.lang")) {
|
|
||||||
caller = element;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return createRegistration(caller, asHandle, priority, ignoreCancelled, eventClass, handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
private <T extends Event> Registration createRegistration(StackTraceElement caller,
|
|
||||||
boolean asHandle,
|
|
||||||
EventPriority priority,
|
|
||||||
boolean ignoreCancelled,
|
|
||||||
Class<T> eventClass,
|
|
||||||
Consumer<? super T> handler) {
|
|
||||||
EventExecutor executor = newEventExecutor(eventClass, handler);
|
|
||||||
/* * /
|
|
||||||
if (asHandle) {
|
|
||||||
return new RegistrationWithHandle(eventClass, caller, executor, priority, plugin, ignoreCancelled);
|
|
||||||
}
|
|
||||||
/* */
|
|
||||||
return new Registration(eventClass, caller, executor, priority, plugin, ignoreCancelled);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void registerAllListeners() {
|
|
||||||
for (Registration registration : registrations) {
|
|
||||||
registration.register();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void unregisterAllListeners() {
|
|
||||||
for (Registration registration : registrations) {
|
|
||||||
registration.unregister();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private Collection<ListenerFieldInfo> getListenerFields(Class<?> clazz, Object instance) {
|
|
||||||
Collection<ListenerFieldInfo> rv = new ArrayList<>();
|
|
||||||
|
|
||||||
Field[] fields = clazz.getDeclaredFields();
|
|
||||||
boolean isStatic = instance == null;
|
|
||||||
if (!isStatic && !clazz.isInstance(instance)) {
|
|
||||||
throw new IllegalArgumentException("Instance must be an instance of the given class");
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldLoop:
|
|
||||||
for (Field f : fields) {
|
|
||||||
if (isStatic != Modifier.isStatic(f.getModifiers())
|
|
||||||
|| !f.isAnnotationPresent(ListenerInfo.class)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!IEventListener.class.isAssignableFrom(f.getType())) {
|
|
||||||
handleListenerFieldError(new ListenerFieldError(f, "Field type cannot be assigned to IEventListener: " + f.getGenericType().getTypeName()));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Type eventType = null;
|
|
||||||
if (f.getType() == IEventListener.class) {
|
|
||||||
|
|
||||||
Type[] typeArgs;
|
|
||||||
if (!(f.getGenericType() instanceof ParameterizedType)
|
|
||||||
|| (typeArgs = ((ParameterizedType) f.getGenericType()).getActualTypeArguments()).length != 1) {
|
|
||||||
// TODO: if its a TypeVariable, in some cases it might be possible to get the type.
|
|
||||||
// Log a warning or throw an exception
|
|
||||||
handleListenerFieldError(new ListenerFieldError(f, "Failed to recognize event class from field type: " + f.getGenericType().getTypeName()));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
eventType = typeArgs[0];
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// field type is subtype of IEventListener.
|
|
||||||
// TODO: link type arguments from field declaration (f.getGenericType()) to matching TypeVariables
|
|
||||||
Type[] interfaces = f.getType().getGenericInterfaces();
|
|
||||||
for (Type itf : interfaces) {
|
|
||||||
Class<?> itfClass;
|
|
||||||
Type[] arguments = null;
|
|
||||||
if (itf instanceof ParameterizedType) {
|
|
||||||
if (!(((ParameterizedType) itf).getRawType() instanceof Class)) {
|
|
||||||
// Should not happen: throw error
|
|
||||||
throw new InternalError("rawType of ParameterizedType expected to be a Class");
|
|
||||||
}
|
|
||||||
itfClass = (Class<?>) ((ParameterizedType) itf).getRawType();
|
|
||||||
arguments = ((ParameterizedType) itf).getActualTypeArguments();
|
|
||||||
} else if (itf instanceof Class<?>) {
|
|
||||||
itfClass = (Class<?>) itf;
|
|
||||||
} else {
|
|
||||||
// TypeVariable? Not sure
|
|
||||||
// Ignore
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (itfClass == IEventListener.class) {
|
|
||||||
if (arguments == null || arguments.length != 1) {
|
|
||||||
// Log a warning or throw an exception
|
|
||||||
handleListenerFieldError(new ListenerFieldError(f, ""));
|
|
||||||
continue fieldLoop;
|
|
||||||
}
|
|
||||||
|
|
||||||
eventType = arguments[0];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (eventType == null) {
|
|
||||||
// Log a warning or throw an exception
|
|
||||||
handleListenerFieldError(new ListenerFieldError(f, "Failed to recognize event class from field type: " + f.getGenericType().getTypeName()));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(eventType instanceof Class)) {
|
|
||||||
if (eventType instanceof ParameterizedType) {
|
|
||||||
Type rawType = ((ParameterizedType) eventType).getRawType();
|
|
||||||
if (!(rawType instanceof Class)) {
|
|
||||||
// Log a warning or throw an exception
|
|
||||||
handleListenerFieldError(new ListenerFieldError(f, "Failed to recognize event class from a Type: " + eventType));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
eventType = rawType;
|
|
||||||
} else {
|
|
||||||
// Log a warning or throw an exception
|
|
||||||
handleListenerFieldError(new ListenerFieldError(f, "Failed to recognize event class from a Type: " + eventType));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Consumer<? super Event> lambda;
|
|
||||||
try {
|
|
||||||
f.setAccessible(true);
|
|
||||||
lambda = (Consumer<? super Event>) f.get(instance);
|
|
||||||
} catch (IllegalArgumentException | IllegalAccessException | ClassCastException e) {
|
|
||||||
// Log a warning or throw an exception
|
|
||||||
handleListenerFieldError(new ListenerFieldError(f, e));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Class<? extends Event> baseEventClass = (Class<? extends Event>) eventType;
|
|
||||||
|
|
||||||
ListenerInfo anno = f.getAnnotation(ListenerInfo.class);
|
|
||||||
String[] eventClassNames = anno.events();
|
|
||||||
if (eventClassNames.length > 0) {
|
|
||||||
|
|
||||||
// The same field might get added here multiple times, to register it with multiple events.
|
|
||||||
// This list is used to prevent adding its listener to the same handler list multiple times.
|
|
||||||
// Allocation of a table is not necessary at this scale.
|
|
||||||
List<HandlerList> handlerLists = new ArrayList<>();
|
|
||||||
|
|
||||||
for (String eventClassName : eventClassNames) {
|
|
||||||
Class<? extends Event> eventClass = getEventClassByName(eventClassName);
|
|
||||||
if (eventClass != null && baseEventClass.isAssignableFrom(eventClass)) {
|
|
||||||
HandlerList handlerList = getHandlerListOf(eventClass);
|
|
||||||
if (handlerList == null) {
|
|
||||||
// multiple warnings could be raised here for the same field
|
|
||||||
handleListenerFieldError(new ListenerFieldError(f, "There is no HandlerList available for the event " + eventClass.getName()));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (handlerLists.contains(handlerList)) {
|
|
||||||
// Ignore: it will work as intended
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
handlerLists.add(handlerList);
|
|
||||||
rv.add(new ListenerFieldInfo(eventClass, lambda, anno));
|
|
||||||
} else {
|
|
||||||
// Error: event class string is not recognized or cannot be assigned to the event type
|
|
||||||
// Log a warning or throw an exception
|
|
||||||
String msg = String.format("Event class '%s', resolved to '%s', is unresolved or cannot be assigned to '%s'",
|
|
||||||
eventClassName, eventClass == null ? null : eventClass.getName(), baseEventClass.getName());
|
|
||||||
handleListenerFieldError(new ListenerFieldError(f, msg));
|
|
||||||
// Don't add the field to the result list
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
rv.add(new ListenerFieldInfo(baseEventClass, lambda, anno));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rv;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void handleListenerFieldError(ListenerFieldError error) {
|
|
||||||
// Log a warning or throw an exception. Behaviour can be changed.
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Class<? extends Event> getEventClassByName(String name) {
|
|
||||||
try {
|
|
||||||
//noinspection unchecked
|
|
||||||
return (Class<? extends Event>) Class.forName("org.bukkit.event." + name);
|
|
||||||
} catch (ClassNotFoundException | ClassCastException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ############################################
|
|
||||||
// # Internal types
|
|
||||||
// ############################################
|
|
||||||
|
|
||||||
/* * /
|
|
||||||
private static final class RegistrationWithHandle extends Registration implements ListenerHandle {
|
|
||||||
RegistrationWithHandle(Class<?> eventClass, StackTraceElement caller, EventExecutor executor, EventPriority priority, Plugin plugin, boolean ignoreCancelled) {
|
|
||||||
super(eventClass, caller, executor, priority, plugin, ignoreCancelled);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void register() {
|
|
||||||
super.register();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void unregister() {
|
|
||||||
super.unregister();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* */
|
|
||||||
|
|
||||||
private static final class HandlerListInfo {
|
|
||||||
final HandlerList handlerList;
|
|
||||||
// true if and only if the handler list resides in a super class of the event for which it was requested.
|
|
||||||
// the filter is needed to filter out event instances not of the requested class.
|
|
||||||
// See newEventExecutor(eventClass, handler)
|
|
||||||
final boolean requiresFilter;
|
|
||||||
|
|
||||||
HandlerListInfo(HandlerList handlerList, boolean requiresFilter) {
|
|
||||||
this.handlerList = handlerList;
|
|
||||||
this.requiresFilter = requiresFilter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class ListenerFieldInfo {
|
|
||||||
final Class<? extends Event> eventClass;
|
|
||||||
final Consumer<? super Event> lambda;
|
|
||||||
final ListenerInfo anno;
|
|
||||||
|
|
||||||
ListenerFieldInfo(Class<? extends Event> eventClass, Consumer<? super Event> lambda, ListenerInfo anno) {
|
|
||||||
this.eventClass = eventClass;
|
|
||||||
this.lambda = lambda;
|
|
||||||
this.anno = anno;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error class to report fields that are intended to be listeners with illegal properties
|
|
||||||
*/
|
|
||||||
static final class ListenerFieldError extends Error {
|
|
||||||
private Field field;
|
|
||||||
|
|
||||||
public ListenerFieldError(Field field, String message) {
|
|
||||||
super(message);
|
|
||||||
this.field = field;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ListenerFieldError(Field field, Throwable cause) {
|
|
||||||
super(cause);
|
|
||||||
this.field = field;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Field getField() {
|
|
||||||
return field;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class RegistratorPlugin implements Plugin {
|
|
||||||
@Override
|
|
||||||
public java.io.File getDataFolder() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public org.bukkit.plugin.PluginDescriptionFile getDescription() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public org.bukkit.configuration.file.FileConfiguration getConfig() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public java.io.InputStream getResource(String s) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void saveConfig() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void saveDefaultConfig() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void saveResource(String s, boolean b) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void reloadConfig() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public org.bukkit.plugin.PluginLoader getPluginLoader() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Server getServer() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEnabled() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDisable() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoad() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onEnable() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isNaggable() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setNaggable(boolean b) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/* * /
|
|
||||||
@Override
|
|
||||||
public com.avaje.ebean.EbeanServer getDatabase() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
/* */
|
|
||||||
@Override
|
|
||||||
public org.bukkit.generator.ChunkGenerator getDefaultWorldGenerator(String s, String s1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public java.util.logging.Logger getLogger() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onCommand(org.bukkit.command.CommandSender commandSender, org.bukkit.command.Command command, String s, String[] strings) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<String> onTabComplete(org.bukkit.command.CommandSender commandSender, org.bukkit.command.Command command, String s, String[] strings) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
return this == obj;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,203 +0,0 @@
|
|||||||
package io.dico.dicore.exceptions;
|
|
||||||
|
|
||||||
import io.dico.dicore.exceptions.checkedfunctions.CheckedFunctionalObject;
|
|
||||||
import io.dico.dicore.exceptions.checkedfunctions.CheckedRunnable;
|
|
||||||
import io.dico.dicore.exceptions.checkedfunctions.CheckedSupplier;
|
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.io.StringWriter;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
@FunctionalInterface
|
|
||||||
public interface ExceptionHandler {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle the given exception according to this handler's implementation
|
|
||||||
*
|
|
||||||
* @param ex The exception to be handled
|
|
||||||
* @throws NullPointerException if ex is null, unless the implementation specifies otherwise
|
|
||||||
* @throws Error ex if ex is an instance of Error, unless the implementation specifies otherwise
|
|
||||||
*/
|
|
||||||
void handle(Throwable ex);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle the given exception according to this handler's implementation
|
|
||||||
* This method is intended for use by {@link CheckedFunctionalObject} and subinterfaces.
|
|
||||||
* It supplies exception handlers the option to acquire more information, by overriding this method and calling it from {@link #handle(Throwable)}
|
|
||||||
*
|
|
||||||
* @param ex The exception to be handled
|
|
||||||
* @param args Any arguments passed, this is used by {@link CheckedFunctionalObject} and subinterfaces.
|
|
||||||
* @return {@code null} (unless specified otherwise by the implementation)
|
|
||||||
* @throws NullPointerException if ex is null, unless the implementation specifies otherwise
|
|
||||||
* @throws Error ex if ex is an instance of Error, unless the implementation specifies otherwise
|
|
||||||
*/
|
|
||||||
default Object handleGenericException(Throwable ex, Object... args) {
|
|
||||||
handle(ex);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return true if this {@link ExceptionHandler}'s {@link #handleGenericException(Throwable, Object...)} method is <b>never</b> expected to throw
|
|
||||||
* an unchecked exception other than {@link Error}
|
|
||||||
*/
|
|
||||||
default boolean isSafe() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Runs the given checked action, handling any thrown exceptions using this exception handler.
|
|
||||||
* <p>
|
|
||||||
* Any exceptions thrown by this handler are delegated to the caller.
|
|
||||||
*
|
|
||||||
* @param action The action to run
|
|
||||||
* @throws NullPointerException if action is null
|
|
||||||
*/
|
|
||||||
default void runSafe(CheckedRunnable<? extends Throwable> action) {
|
|
||||||
Objects.requireNonNull(action);
|
|
||||||
try {
|
|
||||||
action.checkedRun();
|
|
||||||
} catch (Throwable ex) {
|
|
||||||
handle(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Computes the result of the given checked supplier, handling any thrown exceptions using this exception handler.
|
|
||||||
* <p>
|
|
||||||
* Any exceptions thrown by this handler are delegated to the caller.
|
|
||||||
*
|
|
||||||
* @param action The supplier whose result to compute
|
|
||||||
* @param <T> generic type parameter for the supplier and the result type of this method
|
|
||||||
* @return The result of this computation, or null if an error occurred
|
|
||||||
* @throws NullPointerException if action is null
|
|
||||||
*/
|
|
||||||
|
|
||||||
default <T> T supplySafe(CheckedSupplier<T, ? extends Throwable> action) {
|
|
||||||
Objects.requireNonNull(action);
|
|
||||||
try {
|
|
||||||
return action.checkedGet();
|
|
||||||
} catch (Throwable ex) {
|
|
||||||
handle(ex);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param action The action to wrap
|
|
||||||
* @return A runnable that wraps the given action using this handler's {@link #runSafe(CheckedRunnable)} method.
|
|
||||||
* @see #runSafe(CheckedRunnable)
|
|
||||||
*/
|
|
||||||
default Runnable safeRunnable(CheckedRunnable<? extends Throwable> action) {
|
|
||||||
return () -> runSafe(action);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param supplier The computation to wrap
|
|
||||||
* @return A supplier that wraps the given computation using this handler's {@link #supplySafe(CheckedSupplier)} method.
|
|
||||||
* @see #supplySafe(CheckedSupplier)
|
|
||||||
*/
|
|
||||||
default <T> Supplier<T> safeSupplier(CheckedSupplier<T, ? extends Throwable> supplier) {
|
|
||||||
return () -> supplySafe(supplier);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logs the given exception as an error to {@code out}
|
|
||||||
* <p>
|
|
||||||
* Format: Error occurred while {@code failedActivityDescription}, followed by additional details and a stack trace
|
|
||||||
*
|
|
||||||
* @param out The consumer to accept the error message, for instance {@code {@link java.util.logging.Logger logger}::warning}.
|
|
||||||
* @param failedActivityDescription A description of the activity that was being executed when the exception was thrown
|
|
||||||
* @param ex The exception that was thrown
|
|
||||||
* @throws NullPointerException if any argument is null
|
|
||||||
*/
|
|
||||||
static void log(Consumer<String> out, String failedActivityDescription, Throwable ex) {
|
|
||||||
if (out == null || failedActivityDescription == null || ex == null) {
|
|
||||||
throw new NullPointerException();
|
|
||||||
}
|
|
||||||
|
|
||||||
StringWriter msg = new StringWriter(1024);
|
|
||||||
msg.append("Error occurred while ").append(failedActivityDescription).append(':');
|
|
||||||
|
|
||||||
if (ex instanceof SQLException) {
|
|
||||||
SQLException sqlex = (SQLException) ex;
|
|
||||||
msg.append('\n').append("Error code: ").append(Integer.toString(sqlex.getErrorCode()));
|
|
||||||
msg.append('\n').append("SQL State: ").append(sqlex.getSQLState());
|
|
||||||
}
|
|
||||||
|
|
||||||
msg.append('\n').append("=======START STACK=======");
|
|
||||||
try (PrintWriter pw = new PrintWriter(msg)) {
|
|
||||||
ex.printStackTrace(pw);
|
|
||||||
}
|
|
||||||
msg.append('\n').append("========END STACK========");
|
|
||||||
|
|
||||||
out.accept(msg.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param activityDescription The activity description
|
|
||||||
* @return An ExceptionHandler that prints to {@link System#out}
|
|
||||||
* @see #log(Consumer, String)
|
|
||||||
*/
|
|
||||||
static ExceptionHandler log(String activityDescription) {
|
|
||||||
// A method reference would cache the current value in System.out
|
|
||||||
// This would update if the value in System.out is changed (by for example, applying a PrintStream that handles colours).
|
|
||||||
return log(msg -> System.out.println(msg), activityDescription);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param out The Consumer to be passed to {@link #log(Consumer, String, Throwable)}
|
|
||||||
* @param activityDescription The activity description to be passed to {@link #log(Consumer, String, Throwable)}
|
|
||||||
* @return An ExceptionHandler that passes exceptions with given arguments to {@link #log(Consumer, String, Throwable)}
|
|
||||||
* @see #log(Consumer, String, Throwable)
|
|
||||||
*/
|
|
||||||
static ExceptionHandler log(Consumer<String> out, String activityDescription) {
|
|
||||||
return ex -> log(out, activityDescription, ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This ExceptionHandler turns any Throwable into an unchecked exception, then throws it again.
|
|
||||||
*/
|
|
||||||
ExceptionHandler UNCHECKED = new ExceptionHandler() {
|
|
||||||
@Override
|
|
||||||
public void handle(Throwable ex) {
|
|
||||||
errorFilter(ex);
|
|
||||||
if (ex instanceof RuntimeException) {
|
|
||||||
throw (RuntimeException) ex;
|
|
||||||
}
|
|
||||||
throw new RuntimeException(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSafe() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This ExceptionHandler suppresses all exceptions,
|
|
||||||
* apart from {@link Error} because that is not usually supposed to be caught
|
|
||||||
*/
|
|
||||||
ExceptionHandler SUPPRESS = ExceptionHandler::errorFilter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This ExceptionHandler calls {@link Throwable#printStackTrace()} unless it is a {@link NullPointerException} or {@link Error}.
|
|
||||||
*/
|
|
||||||
ExceptionHandler PRINT_UNLESS_NP = ex -> {
|
|
||||||
errorFilter(ex);
|
|
||||||
if (!(ex instanceof NullPointerException)) {
|
|
||||||
ex.printStackTrace();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static void errorFilter(Throwable ex) {
|
|
||||||
if (ex instanceof Error) {
|
|
||||||
throw (Error) ex;
|
|
||||||
} else if (ex == null) {
|
|
||||||
throw new NullPointerException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
package io.dico.dicore.exceptions.checkedfunctions;
|
|
||||||
|
|
||||||
import io.dico.dicore.exceptions.ExceptionHandler;
|
|
||||||
|
|
||||||
import java.util.function.BiConsumer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* checked mimic of {@link BiConsumer}
|
|
||||||
*
|
|
||||||
* @param <TParam1>
|
|
||||||
* @param <TParam2>
|
|
||||||
* @param <TException>
|
|
||||||
*/
|
|
||||||
@FunctionalInterface
|
|
||||||
public interface CheckedBiConsumer<TParam1, TParam2, TException extends Throwable>
|
|
||||||
extends CheckedFunctionalObject<Void, TException>, BiConsumer<TParam1, TParam2> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The consuming action
|
|
||||||
*
|
|
||||||
* @param t the first argument to consume
|
|
||||||
* @param u the second argument to consume
|
|
||||||
* @throws TException if an exception occurs
|
|
||||||
*/
|
|
||||||
void checkedAccept(TParam1 t, TParam2 u) throws TException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* unchecked wrapper for {@link #checkedAccept(Object, Object)}
|
|
||||||
* If a {@link TException} occurs, an unchecked one might be thrown by {@link #resultOnError(Throwable, Object...)}
|
|
||||||
*
|
|
||||||
* @param t the first input
|
|
||||||
* @param u the second input
|
|
||||||
* @see #checkedAccept(Object, Object)
|
|
||||||
* @see #resultOnError(Throwable, Object...)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
default void accept(TParam1 t, TParam2 u) {
|
|
||||||
try {
|
|
||||||
checkedAccept(t, u);
|
|
||||||
} catch (Throwable ex) {
|
|
||||||
handleGenericException(ex, t, u);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
default CheckedBiConsumer<TParam1, TParam2, TException> handleExceptionsWith(ExceptionHandler handler) {
|
|
||||||
return new CheckedBiConsumer<TParam1, TParam2, TException>() {
|
|
||||||
@Override
|
|
||||||
public void checkedAccept(TParam1 t, TParam2 u) throws TException {
|
|
||||||
CheckedBiConsumer.this.checkedAccept(t, u);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Void handleGenericException(Throwable thrown, Object... args) {
|
|
||||||
handler.handleGenericException(thrown, args);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CheckedBiConsumer<TParam1, TParam2, TException> handleExceptionsWith(ExceptionHandler handler) {
|
|
||||||
return CheckedBiConsumer.this.handleExceptionsWith(handler);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
package io.dico.dicore.exceptions.checkedfunctions;
|
|
||||||
|
|
||||||
import io.dico.dicore.exceptions.ExceptionHandler;
|
|
||||||
|
|
||||||
import java.util.function.BiFunction;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* checked mimic of {@link BiFunction}
|
|
||||||
*
|
|
||||||
* @param <TParam1>
|
|
||||||
* @param <TParam2>
|
|
||||||
* @param <TResult>
|
|
||||||
* @param <TException>
|
|
||||||
*/
|
|
||||||
@FunctionalInterface
|
|
||||||
public interface CheckedBiFunction<TParam1, TParam2, TResult, TException extends Throwable>
|
|
||||||
extends CheckedFunctionalObject<TResult, TException>, BiFunction<TParam1, TParam2, TResult> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* the functional method
|
|
||||||
*
|
|
||||||
* @param t the first input
|
|
||||||
* @param u the second input
|
|
||||||
* @return the function output
|
|
||||||
* @throws TException if an exception occurs
|
|
||||||
*/
|
|
||||||
TResult checkedApply(TParam1 t, TParam2 u) throws TException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* unchecked wrapper for {@link #checkedApply(Object, Object)}
|
|
||||||
* If a {@link TException} occurs, an unchecked one might be thrown by {@link #resultOnError(Throwable, Object...)}
|
|
||||||
*
|
|
||||||
* @param t the first input
|
|
||||||
* @param u the second input
|
|
||||||
* @return the function output, or the result from {@link #resultOnError(Throwable, Object...)} if an exception was thrown
|
|
||||||
* @see #checkedApply(Object, Object)
|
|
||||||
* @see #resultOnError(Throwable, Object...)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
default TResult apply(TParam1 t, TParam2 u) {
|
|
||||||
try {
|
|
||||||
return checkedApply(t, u);
|
|
||||||
} catch (Throwable ex) {
|
|
||||||
return handleGenericException(ex, t, u);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
default CheckedBiFunction<TParam1, TParam2, TResult, TException> handleExceptionsWith(ExceptionHandler handler) {
|
|
||||||
return new CheckedBiFunction<TParam1, TParam2, TResult, TException>() {
|
|
||||||
@Override
|
|
||||||
public TResult checkedApply(TParam1 t, TParam2 u) throws TException {
|
|
||||||
return CheckedBiFunction.this.checkedApply(t, u);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public TResult handleGenericException(Throwable thrown, Object... args) {
|
|
||||||
Object result = handler.handleGenericException(thrown, args);
|
|
||||||
try {
|
|
||||||
return (TResult) result;
|
|
||||||
} catch (Exception ex) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CheckedBiFunction<TParam1, TParam2, TResult, TException> handleExceptionsWith(ExceptionHandler handler) {
|
|
||||||
return CheckedBiFunction.this.handleExceptionsWith(handler);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
package io.dico.dicore.exceptions.checkedfunctions;
|
|
||||||
|
|
||||||
import io.dico.dicore.exceptions.ExceptionHandler;
|
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* checked mimic of {@link Consumer}
|
|
||||||
*
|
|
||||||
* @param <TParam>
|
|
||||||
* @param <TException>
|
|
||||||
*/
|
|
||||||
@FunctionalInterface
|
|
||||||
public interface CheckedConsumer<TParam, TException extends Throwable>
|
|
||||||
extends CheckedFunctionalObject<Void, TException>, Consumer<TParam> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The consuming action
|
|
||||||
*
|
|
||||||
* @param t the argument to consume
|
|
||||||
* @throws TException if an error occurs
|
|
||||||
*/
|
|
||||||
void checkedAccept(TParam t) throws TException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unchecked version of {@link #checkedAccept(Object)}
|
|
||||||
* If a {@link TException} occurs, an unchecked one might be thrown by {@link #resultOnError(Throwable, Object...)}
|
|
||||||
*
|
|
||||||
* @param t the argument to consume
|
|
||||||
* @see #checkedAccept(Object)
|
|
||||||
* @see #resultOnError(Throwable, Object...)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
default void accept(TParam t) {
|
|
||||||
try {
|
|
||||||
checkedAccept(t);
|
|
||||||
} catch (Throwable ex) {
|
|
||||||
handleGenericException(ex, t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
default CheckedConsumer<TParam, TException> handleExceptionsWith(ExceptionHandler handler) {
|
|
||||||
return new CheckedConsumer<TParam, TException>() {
|
|
||||||
@Override
|
|
||||||
public void checkedAccept(TParam t) throws TException {
|
|
||||||
CheckedConsumer.this.checkedAccept(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public Void handleGenericException(Throwable thrown, Object... args) {
|
|
||||||
handler.handleGenericException(thrown, args);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CheckedConsumer<TParam, TException> handleExceptionsWith(ExceptionHandler handler) {
|
|
||||||
return CheckedConsumer.this.handleExceptionsWith(handler);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
package io.dico.dicore.exceptions.checkedfunctions;
|
|
||||||
|
|
||||||
import io.dico.dicore.exceptions.ExceptionHandler;
|
|
||||||
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* checked mimic of {@link Function}
|
|
||||||
*
|
|
||||||
* @param <TParam>
|
|
||||||
* @param <TResult>
|
|
||||||
* @param <TException>
|
|
||||||
*/
|
|
||||||
@FunctionalInterface
|
|
||||||
public interface CheckedFunction<TParam, TResult, TException extends Throwable>
|
|
||||||
extends CheckedFunctionalObject<TResult, TException>, Function<TParam, TResult> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* the functional method
|
|
||||||
*
|
|
||||||
* @param t the input
|
|
||||||
* @return the function output
|
|
||||||
* @throws TException if an exception occurs
|
|
||||||
*/
|
|
||||||
TResult checkedApply(TParam t) throws TException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* unchecked wrapper for {@link #checkedApply(Object)}
|
|
||||||
* If a {@link TException} occurs, an unchecked one might be thrown by {@link #resultOnError(Throwable, Object...)}
|
|
||||||
*
|
|
||||||
* @param t the input
|
|
||||||
* @return the function output, or the result from {@link #resultOnError(Throwable, Object...)} if an exception was thrown
|
|
||||||
* @see #checkedApply(Object)
|
|
||||||
* @see #resultOnError(Throwable, Object...)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
default TResult apply(TParam t) {
|
|
||||||
try {
|
|
||||||
return checkedApply(t);
|
|
||||||
} catch (Throwable ex) {
|
|
||||||
return handleGenericException(ex, t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
default CheckedFunction<TParam, TResult, TException> handleExceptionsWith(ExceptionHandler handler) {
|
|
||||||
return new CheckedFunction<TParam, TResult, TException>() {
|
|
||||||
@Override
|
|
||||||
public TResult checkedApply(TParam t) throws TException {
|
|
||||||
return CheckedFunction.this.checkedApply(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public TResult handleGenericException(Throwable thrown, Object... args) {
|
|
||||||
Object result = handler.handleGenericException(thrown, args);
|
|
||||||
try {
|
|
||||||
return (TResult) result;
|
|
||||||
} catch (Exception ex) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CheckedFunction<TParam, TResult, TException> handleExceptionsWith(ExceptionHandler handler) {
|
|
||||||
return CheckedFunction.this.handleExceptionsWith(handler);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
package io.dico.dicore.exceptions.checkedfunctions;
|
|
||||||
|
|
||||||
import io.dico.dicore.exceptions.ExceptionHandler;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base interface for all checked functional interfaces
|
|
||||||
* Most subinterfaces will mimic interfaces in the package {@link java.util.function}
|
|
||||||
* <p>
|
|
||||||
* Checked functional interfaces are functions with throws declarations.
|
|
||||||
* The name comes from the fact that they can throw <b>checked</b> exceptions.
|
|
||||||
* <p>
|
|
||||||
* They extend their non-checked counterparts, whose methods are implemented by
|
|
||||||
* returning the result of {@link #resultOnError(TException, Object...)} when an exception is thrown
|
|
||||||
* <p>
|
|
||||||
* Made public to allow more specialized checked functional interfaces to subclass it.
|
|
||||||
* Making primitive versions shouldn't provide a significant performance increase because we're checking for exceptions,
|
|
||||||
* the performance impact of which is probably a few magnitudes larger. Don't quote me on this.
|
|
||||||
*
|
|
||||||
* @param <TResult> The return type of this functional interface's method
|
|
||||||
* @param <TException> The type of exception that might be thrown
|
|
||||||
*/
|
|
||||||
public interface CheckedFunctionalObject<TResult, TException extends Throwable> extends ExceptionHandler {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*
|
|
||||||
* @param ex The exception to be handled
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
default void handle(Throwable ex) {
|
|
||||||
handleGenericException(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
* <p>
|
|
||||||
* Method to handle exceptions thrown by the default implementations of subinterfaces.
|
|
||||||
* Since you can't catch a type parameter as exception, this code is in place to take care of it.
|
|
||||||
* <p>
|
|
||||||
* If the thrown exception is not a TException, an unchecked version is thrown by calling this method.
|
|
||||||
*
|
|
||||||
* @param thrown The thrown exception
|
|
||||||
* @param args the arguments supplied to the method that threw the exception, if any. These are not guaranteed to be given if parameters are present.
|
|
||||||
* @return The result computed by {@link #resultOnError(Throwable, Object...)}
|
|
||||||
* @see #resultOnError(Throwable, Object...)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
default TResult handleGenericException(Throwable thrown, Object... args) {
|
|
||||||
|
|
||||||
// check if the throwable is a TException
|
|
||||||
TException castedException;
|
|
||||||
try {
|
|
||||||
castedException = (TException) thrown;
|
|
||||||
} catch (ClassCastException ex) {
|
|
||||||
// if not, throw an unchecked version of it
|
|
||||||
ExceptionHandler.UNCHECKED.handleGenericException(thrown);
|
|
||||||
// this code is never reached.
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if it is a TException, use resultOnError to compute result.
|
|
||||||
return resultOnError(castedException, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method handles the exceptions thrown by the checked method of this object, when called by its unchecked wrapper.
|
|
||||||
*
|
|
||||||
* @param ex The exception thrown
|
|
||||||
* @param args The (typed) arguments passed, if any
|
|
||||||
* @return The result to return, if any
|
|
||||||
*/
|
|
||||||
default TResult resultOnError(TException ex, Object... args) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new functional object that uses the given exception handler to handle exceptions.
|
|
||||||
*
|
|
||||||
* @param handler The handler to handle exceptions
|
|
||||||
* @return a new functional object that uses the given exception handler to handle exceptions
|
|
||||||
*/
|
|
||||||
CheckedFunctionalObject<TResult, TException> handleExceptionsWith(ExceptionHandler handler);
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
package io.dico.dicore.exceptions.checkedfunctions;
|
|
||||||
|
|
||||||
import io.dico.dicore.exceptions.ExceptionHandler;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* checked mimic of {@link Runnable}
|
|
||||||
*
|
|
||||||
* @param <TException>
|
|
||||||
*/
|
|
||||||
@FunctionalInterface
|
|
||||||
public interface CheckedRunnable<TException extends Throwable>
|
|
||||||
extends CheckedFunctionalObject<Void, TException>, Runnable {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The runnable action
|
|
||||||
*
|
|
||||||
* @throws TException if an exception occurs
|
|
||||||
*/
|
|
||||||
void checkedRun() throws TException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unchecked version of {@link #checkedRun()}
|
|
||||||
* If a {@link TException} occurs, an unchecked one might be thrown by {@link #resultOnError(Throwable, Object...)}
|
|
||||||
*
|
|
||||||
* @see #checkedRun()
|
|
||||||
* @see #resultOnError(Throwable, Object...)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
default void run() {
|
|
||||||
try {
|
|
||||||
checkedRun();
|
|
||||||
} catch (Throwable ex) {
|
|
||||||
handleGenericException(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
default CheckedRunnable<TException> handleExceptionsWith(ExceptionHandler handler) {
|
|
||||||
return new CheckedRunnable<TException>() {
|
|
||||||
@Override
|
|
||||||
public void checkedRun() throws TException {
|
|
||||||
CheckedRunnable.this.checkedRun();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public Void handleGenericException(Throwable thrown, Object... args) {
|
|
||||||
handler.handleGenericException(thrown, args);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CheckedRunnable<TException> handleExceptionsWith(ExceptionHandler handler) {
|
|
||||||
return CheckedRunnable.this.handleExceptionsWith(handler);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
package io.dico.dicore.exceptions.checkedfunctions;
|
|
||||||
|
|
||||||
import io.dico.dicore.exceptions.ExceptionHandler;
|
|
||||||
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* checked mimic of {@link Supplier}
|
|
||||||
*
|
|
||||||
* @param <TResult>
|
|
||||||
* @param <TException>
|
|
||||||
*/
|
|
||||||
@FunctionalInterface
|
|
||||||
public interface CheckedSupplier<TResult, TException extends Throwable>
|
|
||||||
extends CheckedFunctionalObject<TResult, TException>, Supplier<TResult> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The computation
|
|
||||||
*
|
|
||||||
* @return the result of this computation
|
|
||||||
* @throws TException if an error occurs
|
|
||||||
*/
|
|
||||||
TResult checkedGet() throws TException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unchecked version of {@link #checkedGet()}
|
|
||||||
* If a {@link TException} occurs, an unchecked one might be thrown by {@link #resultOnError(Throwable, Object...)}
|
|
||||||
*
|
|
||||||
* @return the result of this computation
|
|
||||||
* @see #checkedGet()
|
|
||||||
* @see #resultOnError(Throwable, Object...)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
default TResult get() {
|
|
||||||
try {
|
|
||||||
return checkedGet();
|
|
||||||
} catch (Throwable ex) {
|
|
||||||
return handleGenericException(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
default CheckedSupplier<TResult, TException> handleExceptionsWith(ExceptionHandler handler) {
|
|
||||||
return new CheckedSupplier<TResult, TException>() {
|
|
||||||
@Override
|
|
||||||
public TResult checkedGet() throws TException {
|
|
||||||
return CheckedSupplier.this.checkedGet();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public TResult handleGenericException(Throwable thrown, Object... args) {
|
|
||||||
Object result = handler.handleGenericException(thrown, args);
|
|
||||||
try {
|
|
||||||
return (TResult) result;
|
|
||||||
} catch (Exception ex) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CheckedSupplier<TResult, TException> handleExceptionsWith(ExceptionHandler handler) {
|
|
||||||
return CheckedSupplier.this.handleExceptionsWith(handler);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
package io.dico.parcels2
|
||||||
|
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
|
import io.dico.parcels2.storage.Storage
|
||||||
|
import io.dico.parcels2.storage.StorageFactory
|
||||||
|
import io.dico.parcels2.storage.yamlObjectMapper
|
||||||
|
import org.bukkit.Bukkit
|
||||||
|
import org.bukkit.Bukkit.createBlockData
|
||||||
|
import org.bukkit.GameMode
|
||||||
|
import org.bukkit.Material
|
||||||
|
import org.bukkit.block.Biome
|
||||||
|
import org.bukkit.block.data.BlockData
|
||||||
|
import java.io.Reader
|
||||||
|
import java.io.Writer
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class Options {
|
||||||
|
var worlds: Map<String, WorldOptions> = HashMap()
|
||||||
|
private set
|
||||||
|
var storage: StorageOptions = StorageOptions("mysql", DataConnectionOptions())
|
||||||
|
|
||||||
|
fun addWorld(name: String, options: WorldOptions) = (worlds as MutableMap).put(name, options)
|
||||||
|
|
||||||
|
fun addDefaultWorld() = addWorld("plotworld", WorldOptions())
|
||||||
|
|
||||||
|
fun writeTo(writer: Writer) = yamlObjectMapper.writeValue(writer, this)
|
||||||
|
|
||||||
|
fun mergeFrom(reader: Reader) = yamlObjectMapper.readerForUpdating(this).readValue<Options>(reader)
|
||||||
|
|
||||||
|
override fun toString(): String = yamlObjectMapper.writeValueAsString(this)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
data class WorldOptions(var gameMode: GameMode? = GameMode.CREATIVE,
|
||||||
|
var dayTime: Boolean = true,
|
||||||
|
var noWeather: Boolean = true,
|
||||||
|
var dropEntityItems: Boolean = true,
|
||||||
|
var doTileDrops: Boolean = false,
|
||||||
|
var disableExplosions: Boolean = true,
|
||||||
|
var blockPortalCreation: Boolean = true,
|
||||||
|
var blockMobSpawning: Boolean = true,
|
||||||
|
var blockedItems: Set<Material> = EnumSet.of(Material.FLINT_AND_STEEL, Material.SNOWBALL),
|
||||||
|
var axisLimit: Int = 10,
|
||||||
|
var generator: GeneratorOptions = DefaultGeneratorOptions()) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class GeneratorOptions {
|
||||||
|
|
||||||
|
abstract fun generatorFactory(): GeneratorFactory
|
||||||
|
|
||||||
|
fun getGenerator(worldName: String) = generatorFactory().newParcelGenerator(worldName, this)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
data class DefaultGeneratorOptions(var defaultBiome: Biome = Biome.JUNGLE,
|
||||||
|
var wallType: BlockData = createBlockData(Material.STONE_SLAB),
|
||||||
|
var floorType: BlockData = createBlockData(Material.QUARTZ_BLOCK),
|
||||||
|
var fillType: BlockData = createBlockData(Material.QUARTZ_BLOCK),
|
||||||
|
var pathMainType: BlockData = createBlockData(Material.SANDSTONE),
|
||||||
|
var pathAltType: BlockData = createBlockData(Material.REDSTONE_BLOCK),
|
||||||
|
var parcelSize: Int = 101,
|
||||||
|
var pathSize: Int = 9,
|
||||||
|
var floorHeight: Int = 64,
|
||||||
|
var offsetX: Int = 0,
|
||||||
|
var offsetZ: Int = 0) : GeneratorOptions() {
|
||||||
|
|
||||||
|
override fun generatorFactory(): GeneratorFactory = DefaultParcelGenerator.Factory
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class StorageOptions(val dialect: String,
|
||||||
|
val options: Any) {
|
||||||
|
|
||||||
|
@get:JsonIgnore
|
||||||
|
val factory = StorageFactory.getFactory(dialect) ?: throw IllegalArgumentException("Invalid storage dialect: $dialect")
|
||||||
|
|
||||||
|
fun newStorageInstance(): Storage = factory.newStorageInstance(dialect, options)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
data class DataConnectionOptions(val address: String = "localhost",
|
||||||
|
val database: String = "parcels",
|
||||||
|
val username: String = "root",
|
||||||
|
val password: String = "",
|
||||||
|
val poolSize: Int = 4) {
|
||||||
|
|
||||||
|
fun splitAddressAndPort(defaultPort: Int = 3306): Pair<String, Int>? {
|
||||||
|
val idx = address.indexOf(":").takeUnless { it == -1 } ?: return Pair(address, defaultPort)
|
||||||
|
|
||||||
|
val addressName = address.substring(0, idx).takeUnless { it.isBlank() } ?: return null.also {
|
||||||
|
logger.error("(Invalidly) blank address in data storage options")
|
||||||
|
}
|
||||||
|
|
||||||
|
val port = address.substring(idx).toIntOrNull() ?: return null.also {
|
||||||
|
logger.error("Invalid port number in data storage options: $it, using $defaultPort as default")
|
||||||
|
}
|
||||||
|
|
||||||
|
return Pair(addressName, port)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
data class DataFileOptions(val location: String = "/flatfile-storage/")
|
||||||
@@ -2,6 +2,7 @@ package io.dico.parcels2
|
|||||||
|
|
||||||
import io.dico.parcels2.math.Vec2i
|
import io.dico.parcels2.math.Vec2i
|
||||||
import io.dico.parcels2.util.getPlayerName
|
import io.dico.parcels2.util.getPlayerName
|
||||||
|
import io.dico.parcels2.util.hasBuildAnywhere
|
||||||
import org.bukkit.Bukkit
|
import org.bukkit.Bukkit
|
||||||
import org.bukkit.entity.Player
|
import org.bukkit.entity.Player
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -10,13 +11,29 @@ class Parcel(val world: ParcelWorld,
|
|||||||
val pos: Vec2i,
|
val pos: Vec2i,
|
||||||
var data: ParcelData = ParcelData()) {
|
var data: ParcelData = ParcelData()) {
|
||||||
|
|
||||||
|
val id get() = "${pos.x}:${pos.z}"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class ParcelData {
|
class ParcelData {
|
||||||
val owner: ParcelOwner? = null
|
private val added = mutableMapOf<UUID, AddedStatus>()
|
||||||
val added = mutableMapOf<UUID, Boolean>()
|
var owner: ParcelOwner? = null
|
||||||
|
|
||||||
|
fun setAddedStatus(uuid: UUID): AddedStatus = added.getOrDefault(uuid, AddedStatus.DEFAULT)
|
||||||
|
fun setAddedStatus(uuid: UUID, state: AddedStatus) = state.takeIf { it != AddedStatus.DEFAULT }?.let { added[uuid] = it }
|
||||||
|
?: added.remove(uuid)
|
||||||
|
|
||||||
|
fun isBanned(uuid: UUID) = setAddedStatus(uuid) == AddedStatus.BANNED
|
||||||
|
fun isAllowed(uuid: UUID) = setAddedStatus(uuid) == AddedStatus.ALLOWED
|
||||||
|
fun canBuild(player: Player) = isAllowed(player.uniqueId)
|
||||||
|
|| owner?.matches(player, allowNameMatch = false) ?: false
|
||||||
|
|| player.hasBuildAnywhere
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class AddedStatus {
|
||||||
|
DEFAULT,
|
||||||
|
ALLOWED,
|
||||||
|
BANNED
|
||||||
}
|
}
|
||||||
|
|
||||||
data class ParcelOwner(val uuid: UUID? = null,
|
data class ParcelOwner(val uuid: UUID? = null,
|
||||||
@@ -29,11 +46,13 @@ data class ParcelOwner(val uuid: UUID? = null,
|
|||||||
val playerName get() = getPlayerName(uuid, name)
|
val playerName get() = getPlayerName(uuid, name)
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
val offlinePlayer get() = (uuid?.let { Bukkit.getOfflinePlayer(it) } ?: Bukkit.getOfflinePlayer(name))
|
val offlinePlayer
|
||||||
|
get() = (uuid?.let { Bukkit.getOfflinePlayer(it) } ?: Bukkit.getOfflinePlayer(name))
|
||||||
?.takeIf { it.isOnline() || it.hasPlayedBefore() }
|
?.takeIf { it.isOnline() || it.hasPlayedBefore() }
|
||||||
|
|
||||||
fun matches(player: Player, allowNameMatch: Boolean = false): Boolean {
|
fun matches(player: Player, allowNameMatch: Boolean = false): Boolean {
|
||||||
return player.uniqueId == uuid || (allowNameMatch && player.name == name)
|
return uuid?.let { it == player.uniqueId } ?: false
|
||||||
|
|| (allowNameMatch && name?.let { it == player.name } ?: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,174 @@
|
|||||||
package io.dico.parcels2
|
package io.dico.parcels2
|
||||||
|
|
||||||
class ParcelWorld {
|
import io.dico.parcels2.math.Vec2i
|
||||||
|
import io.dico.parcels2.math.floor
|
||||||
|
import kotlinx.coroutines.experimental.launch
|
||||||
|
import org.bukkit.Bukkit
|
||||||
|
import org.bukkit.Location
|
||||||
|
import org.bukkit.World
|
||||||
|
import org.bukkit.WorldCreator
|
||||||
|
import org.bukkit.block.Block
|
||||||
|
import org.bukkit.entity.Entity
|
||||||
|
import org.bukkit.entity.Player
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.coroutines.experimental.buildSequence
|
||||||
|
|
||||||
|
val worlds: Map<String, ParcelWorld> get() = _worlds
|
||||||
|
private val _worlds: MutableMap<String, ParcelWorld> = HashMap()
|
||||||
|
|
||||||
|
fun getWorld(name: String): ParcelWorld? = _worlds.get(name)
|
||||||
|
|
||||||
|
fun getWorld(world: World): ParcelWorld? = getWorld(world.name)
|
||||||
|
|
||||||
|
fun getParcelAt(block: Block): Parcel? = getParcelAt(block.world, block.x, block.z)
|
||||||
|
|
||||||
|
fun getParcelAt(player: Player): Parcel? = getParcelAt(player.location)
|
||||||
|
|
||||||
|
fun getParcelAt(location: Location): Parcel? = getParcelAt(location.world, location.x.floor(), location.z.floor())
|
||||||
|
|
||||||
|
fun getParcelAt(world: World, x: Int, z: Int): Parcel? = getParcelAt(world.name, x, z)
|
||||||
|
|
||||||
|
fun getParcelAt(world: String, x: Int, z: Int): Parcel? {
|
||||||
|
with (getWorld(world) ?: return null) {
|
||||||
|
return generator.parcelAt(x, z)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadWorlds(options: Options) {
|
||||||
|
for ((worldName, worldOptions) in options.worlds.entries) {
|
||||||
|
val world: ParcelWorld
|
||||||
|
try {
|
||||||
|
world = ParcelWorld(worldName, worldOptions, worldOptions.generator.getGenerator(worldName))
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
ex.printStackTrace()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_worlds.put(worldName, world)
|
||||||
|
|
||||||
|
if (Bukkit.getWorld(worldName) == null) {
|
||||||
|
val bworld = WorldCreator(worldName).generator(world.generator).createWorld()
|
||||||
|
val spawn = world.generator.getFixedSpawnLocation(bworld, null)
|
||||||
|
bworld.setSpawnLocation(spawn.x.floor(), spawn.y.floor(), spawn.z.floor())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ParcelProvider {
|
||||||
|
|
||||||
|
fun parcelAt(x: Int, z: Int): Parcel?
|
||||||
|
|
||||||
|
fun parcelAt(vec: Vec2i): Parcel? = parcelAt(vec.x, vec.z)
|
||||||
|
|
||||||
|
fun parcelAt(loc: Location): Parcel? = parcelAt(loc.x.floor(), loc.z.floor())
|
||||||
|
|
||||||
|
fun parcelAt(entity: Entity): Parcel? = parcelAt(entity.location)
|
||||||
|
|
||||||
|
fun parcelAt(block: Block): Parcel? = parcelAt(block.x, block.z)
|
||||||
|
}
|
||||||
|
|
||||||
|
class ParcelWorld(val name: String,
|
||||||
|
val options: WorldOptions,
|
||||||
|
val generator: ParcelGenerator) : ParcelProvider by generator {
|
||||||
|
val world: World by lazy {
|
||||||
|
val tmp = Bukkit.getWorld(name)
|
||||||
|
if (tmp == null) {
|
||||||
|
throw NullPointerException("World $name does not appear to be loaded")
|
||||||
|
}
|
||||||
|
tmp
|
||||||
|
}
|
||||||
|
|
||||||
|
val container: ParcelContainer = DefaultParcelContainer(this)
|
||||||
|
|
||||||
|
fun parcelByID(x: Int, z: Int): Parcel? {
|
||||||
|
TODO("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parcelByID(id: Vec2i): Parcel? = parcelByID(id.x, id.z)
|
||||||
|
|
||||||
|
fun enforceOptionsIfApplicable() {
|
||||||
|
val world = world
|
||||||
|
val options = options
|
||||||
|
if (options.dayTime) {
|
||||||
|
world.setGameRuleValue("doDaylightCycle", "false")
|
||||||
|
world.setTime(6000)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.noWeather) {
|
||||||
|
world.setStorm(false)
|
||||||
|
world.setThundering(false)
|
||||||
|
world.weatherDuration = Integer.MAX_VALUE
|
||||||
|
}
|
||||||
|
|
||||||
|
world.setGameRuleValue("doTileDrops", "${options.doTileDrops}")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class ParcelContainer {
|
||||||
|
|
||||||
|
abstract fun ployByID(x: Int, z: Int): Parcel?
|
||||||
|
|
||||||
|
abstract fun nextEmptyParcel(): Parcel?
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class DefaultParcelContainer(private val world: ParcelWorld) : ParcelContainer() {
|
||||||
|
private var parcels: Array<Array<Parcel>>
|
||||||
|
|
||||||
|
init {
|
||||||
|
parcels = initArray(world.options.axisLimit, world)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resizeIfSizeChanged() {
|
||||||
|
if (parcels.size / 2 != world.options.axisLimit) {
|
||||||
|
resize(world.options.axisLimit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resize(axisLimit: Int) {
|
||||||
|
parcels = initArray(axisLimit, world, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun initArray(axisLimit: Int, world: ParcelWorld, cur: DefaultParcelContainer? = null): Array<Array<Parcel>> {
|
||||||
|
val arraySize = 2 * axisLimit + 1
|
||||||
|
return Array(arraySize, {
|
||||||
|
val x = it - axisLimit
|
||||||
|
Array(arraySize, {
|
||||||
|
val z = it - axisLimit
|
||||||
|
cur?.ployByID(x, z) ?: Parcel(world, Vec2i(x, z))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun ployByID(x: Int, z: Int): Parcel? {
|
||||||
|
return parcels[x][z]
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun nextEmptyParcel(): Parcel? {
|
||||||
|
TODO()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun allParcels(): Sequence<Parcel> = buildSequence {
|
||||||
|
for (array in parcels) {
|
||||||
|
yieldAll(array.iterator())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadAllData() {
|
||||||
|
/*
|
||||||
|
val channel = Main.instance.storage.readParcelData(allParcels(), 100).channel
|
||||||
|
launch(Main.instance.storage.asyncDispatcher) {
|
||||||
|
for ((parcel, data) in channel) {
|
||||||
|
if (data != null) {
|
||||||
|
parcel.data = data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
TODO()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,15 @@
|
|||||||
package io.dico.parcels2
|
package io.dico.parcels2
|
||||||
|
|
||||||
import org.bukkit.plugin.java.JavaPlugin
|
import org.bukkit.plugin.java.JavaPlugin
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
|
val logger = LoggerFactory.getLogger("ParcelsPlugin")
|
||||||
|
|
||||||
class ParcelsPlugin : JavaPlugin() {
|
class ParcelsPlugin : JavaPlugin() {
|
||||||
|
|
||||||
|
override fun onEnable() {
|
||||||
|
super.onEnable()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,263 @@
|
|||||||
|
package io.dico.parcels2
|
||||||
|
|
||||||
|
import io.dico.parcels2.math.Vec2i
|
||||||
|
import io.dico.parcels2.math.clamp
|
||||||
|
import io.dico.parcels2.math.even
|
||||||
|
import io.dico.parcels2.math.umod
|
||||||
|
import org.bukkit.*
|
||||||
|
import org.bukkit.Bukkit.createBlockData
|
||||||
|
import org.bukkit.block.Biome
|
||||||
|
import org.bukkit.block.Block
|
||||||
|
import org.bukkit.block.BlockFace
|
||||||
|
import org.bukkit.block.Skull
|
||||||
|
import org.bukkit.block.data.BlockData
|
||||||
|
import org.bukkit.block.data.type.Sign
|
||||||
|
import org.bukkit.block.data.type.Slab
|
||||||
|
import org.bukkit.entity.Entity
|
||||||
|
import org.bukkit.generator.BlockPopulator
|
||||||
|
import org.bukkit.generator.ChunkGenerator
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.coroutines.experimental.buildIterator
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
abstract class ParcelGenerator : ChunkGenerator(), ParcelProvider {
|
||||||
|
abstract val world: ParcelWorld
|
||||||
|
|
||||||
|
abstract val factory: GeneratorFactory
|
||||||
|
|
||||||
|
abstract override fun generateChunkData(world: World?, random: Random?, chunkX: Int, chunkZ: Int, biome: BiomeGrid?): ChunkData
|
||||||
|
|
||||||
|
abstract fun populate(world: World?, random: Random?, chunk: Chunk?)
|
||||||
|
|
||||||
|
abstract override fun getFixedSpawnLocation(world: World?, random: Random?): Location
|
||||||
|
|
||||||
|
override fun getDefaultPopulators(world: World?): MutableList<BlockPopulator> {
|
||||||
|
return Collections.singletonList(object : BlockPopulator() {
|
||||||
|
override fun populate(world: World?, random: Random?, chunk: Chunk?) {
|
||||||
|
this@ParcelGenerator.populate(world, random, chunk)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract fun updateOwner(parcel: Parcel)
|
||||||
|
|
||||||
|
abstract fun getBottomCoord(parcel: Parcel): Vec2i
|
||||||
|
|
||||||
|
abstract fun getHomeLocation(parcel: Parcel): Location
|
||||||
|
|
||||||
|
abstract fun setBiome(parcel: Parcel, biome: Biome)
|
||||||
|
|
||||||
|
abstract fun getEntities(parcel: Parcel): Collection<Entity>
|
||||||
|
|
||||||
|
abstract fun getBlocks(parcel: Parcel, yRange: IntRange = 0..255): Iterator<Block>
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GeneratorFactory {
|
||||||
|
companion object GeneratorFactories {
|
||||||
|
private val map: MutableMap<String, GeneratorFactory> = HashMap()
|
||||||
|
|
||||||
|
fun registerFactory(generator: GeneratorFactory): Boolean = map.putIfAbsent(generator.name, generator) == null
|
||||||
|
|
||||||
|
fun getFactory(name: String): GeneratorFactory? = map.get(name)
|
||||||
|
|
||||||
|
init {
|
||||||
|
registerFactory(DefaultParcelGenerator.Factory)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
val name: String
|
||||||
|
|
||||||
|
val optionsClass: KClass<out GeneratorOptions>
|
||||||
|
|
||||||
|
fun newParcelGenerator(worldName: String, options: GeneratorOptions): ParcelGenerator
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class DefaultParcelGenerator(name: String, private val o: DefaultGeneratorOptions) : ParcelGenerator() {
|
||||||
|
override val world: ParcelWorld by lazy { TODO() }
|
||||||
|
override val factory = Factory
|
||||||
|
|
||||||
|
companion object Factory : GeneratorFactory {
|
||||||
|
override val name get() = "default"
|
||||||
|
override val optionsClass get() = DefaultGeneratorOptions::class
|
||||||
|
override fun newParcelGenerator(worldName: String, options: GeneratorOptions): ParcelGenerator {
|
||||||
|
return DefaultParcelGenerator(worldName, options as DefaultGeneratorOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val sectionSize = o.parcelSize + o.pathSize
|
||||||
|
val pathOffset = (if (o.pathSize % 2 == 0) o.pathSize + 2 else o.pathSize + 1) / 2
|
||||||
|
val makePathMain = o.pathSize > 2
|
||||||
|
val makePathAlt = o.pathSize > 4
|
||||||
|
|
||||||
|
private inline fun <T> generate(chunkX: Int,
|
||||||
|
chunkZ: Int,
|
||||||
|
floor: T, wall:
|
||||||
|
T, pathMain: T,
|
||||||
|
pathAlt: T,
|
||||||
|
fill: T,
|
||||||
|
setter: (Int, Int, Int, T) -> Unit) {
|
||||||
|
|
||||||
|
val floorHeight = o.floorHeight
|
||||||
|
val parcelSize = o.parcelSize
|
||||||
|
val sectionSize = sectionSize
|
||||||
|
val pathOffset = pathOffset
|
||||||
|
val makePathMain = makePathMain
|
||||||
|
val makePathAlt = makePathAlt
|
||||||
|
|
||||||
|
// parcel bottom x and z
|
||||||
|
// umod is unsigned %: the result is always >= 0
|
||||||
|
val pbx = ((chunkX shl 4) - o.offsetX) umod sectionSize
|
||||||
|
val pbz = ((chunkZ shl 4) - o.offsetZ) umod sectionSize
|
||||||
|
|
||||||
|
var curHeight: Int
|
||||||
|
var x: Int
|
||||||
|
var z: Int
|
||||||
|
for (cx in 0..15) {
|
||||||
|
for (cz in 0..15) {
|
||||||
|
x = (pbx + cx) % sectionSize - pathOffset
|
||||||
|
z = (pbz + cz) % sectionSize - pathOffset
|
||||||
|
curHeight = floorHeight
|
||||||
|
|
||||||
|
val type = when {
|
||||||
|
(x in 0 until parcelSize && z in 0 until parcelSize) -> floor
|
||||||
|
(x in -1..parcelSize && z in -1..parcelSize) -> {
|
||||||
|
curHeight++
|
||||||
|
wall
|
||||||
|
}
|
||||||
|
(makePathAlt && x in -2 until parcelSize + 2 && z in -2 until parcelSize + 2) -> pathAlt
|
||||||
|
(makePathMain) -> pathMain
|
||||||
|
else -> {
|
||||||
|
curHeight++
|
||||||
|
wall
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (y in 0 until curHeight) {
|
||||||
|
setter(cx, y, cz, fill)
|
||||||
|
}
|
||||||
|
setter(cx, curHeight, cz, type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun generateChunkData(world: World?, random: Random?, chunkX: Int, chunkZ: Int, biome: BiomeGrid?): ChunkData {
|
||||||
|
val out = Bukkit.createChunkData(world)
|
||||||
|
generate(chunkX, chunkZ, o.floorType, o.wallType, o.pathMainType, o.pathAltType, o.fillType) { x, y, z, type ->
|
||||||
|
out.setBlock(x, y, z, type)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun populate(world: World?, random: Random?, chunk: Chunk?) {
|
||||||
|
/*
|
||||||
|
generate(chunk!!.x, chunk.z, o.floorType.data, o.wallType.data, o.pathMainType.data, o.pathAltType.data, o.fillType.data) { x, y, z, type ->
|
||||||
|
if (type == 0.toByte()) chunk.getBlock(x, y, z).setData(type, false)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFixedSpawnLocation(world: World?, random: Random?): Location {
|
||||||
|
val fix = if (o.parcelSize.even) 0.5 else 0.0
|
||||||
|
return Location(world, o.offsetX + fix, o.floorHeight + 1.0, o.offsetZ + fix)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun parcelAt(x: Int, z: Int): Parcel? {
|
||||||
|
val sectionSize = sectionSize
|
||||||
|
val parcelSize = o.parcelSize
|
||||||
|
val absX = x - o.offsetX - pathOffset
|
||||||
|
val absZ = z - o.offsetZ - pathOffset
|
||||||
|
val modX = absX umod sectionSize
|
||||||
|
val modZ = absZ umod sectionSize
|
||||||
|
if (0 <= modX && modX < parcelSize && 0 <= modZ && modZ < parcelSize) {
|
||||||
|
return world.parcelByID((absX - modX) / sectionSize, (absZ - modZ) / sectionSize)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getBottomCoord(parcel: Parcel): Vec2i = Vec2i(sectionSize * parcel.pos.x + pathOffset + o.offsetX,
|
||||||
|
sectionSize * parcel.pos.z + pathOffset + o.offsetZ)
|
||||||
|
|
||||||
|
override fun getHomeLocation(parcel: Parcel): Location {
|
||||||
|
val bottom = getBottomCoord(parcel)
|
||||||
|
return Location(world.world, bottom.x.toDouble(), o.floorHeight + 1.0, bottom.z + (o.parcelSize - 1) / 2.0, -90F, 0F)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateOwner(parcel: Parcel) {
|
||||||
|
val world = this.world.world
|
||||||
|
val b = getBottomCoord(parcel)
|
||||||
|
|
||||||
|
val wallBlock = world.getBlockAt(b.x - 1, o.floorHeight + 1, b.z - 1)
|
||||||
|
val signBlock = world.getBlockAt(b.x - 2, o.floorHeight + 1, b.z - 1)
|
||||||
|
val skullBlock = world.getBlockAt(b.x - 1, o.floorHeight + 2, b.z - 1)
|
||||||
|
|
||||||
|
val owner = parcel.data?.owner
|
||||||
|
if (owner == null) {
|
||||||
|
wallBlock.blockData = o.wallType
|
||||||
|
signBlock.type = Material.AIR
|
||||||
|
skullBlock.type = Material.AIR
|
||||||
|
} else {
|
||||||
|
|
||||||
|
val wallBlockType: BlockData = if (o.wallType is Slab)
|
||||||
|
(o.wallType.clone() as Slab).apply { type = Slab.Type.DOUBLE }
|
||||||
|
else
|
||||||
|
o.wallType
|
||||||
|
|
||||||
|
wallBlock.blockData = wallBlockType
|
||||||
|
|
||||||
|
signBlock.blockData = (createBlockData(Material.WALL_SIGN) as Sign).apply { rotation = BlockFace.NORTH }
|
||||||
|
|
||||||
|
val sign = signBlock.state as org.bukkit.block.Sign
|
||||||
|
sign.setLine(0, parcel.id)
|
||||||
|
sign.setLine(2, owner.playerName)
|
||||||
|
sign.update()
|
||||||
|
|
||||||
|
skullBlock.type = Material.PLAYER_HEAD
|
||||||
|
val skull = skullBlock.state as Skull
|
||||||
|
if (owner.uuid != null) {
|
||||||
|
skull.owningPlayer = owner.offlinePlayer
|
||||||
|
} else {
|
||||||
|
skull.owner = owner.name
|
||||||
|
}
|
||||||
|
skull.rotation = BlockFace.WEST
|
||||||
|
skull.update()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setBiome(parcel: Parcel, biome: Biome) {
|
||||||
|
val world = this.world.world
|
||||||
|
val b = getBottomCoord(parcel)
|
||||||
|
val parcelSize = o.parcelSize
|
||||||
|
for (x in b.x until b.x + parcelSize) {
|
||||||
|
for (z in b.z until b.z + parcelSize) {
|
||||||
|
world.setBiome(x, z, biome)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getEntities(parcel: Parcel): Collection<Entity> {
|
||||||
|
val world = this.world.world
|
||||||
|
val b = getBottomCoord(parcel)
|
||||||
|
val parcelSize = o.parcelSize
|
||||||
|
val center = Location(world, (b.x + parcelSize) / 2.0, 128.0, (b.z + parcelSize) / 2.0)
|
||||||
|
return world.getNearbyEntities(center, parcelSize / 2.0 + 0.2, 128.0, parcelSize / 2.0 + 0.2)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getBlocks(parcel: Parcel, yRange: IntRange): Iterator<Block> = buildIterator {
|
||||||
|
val range = yRange.clamp(0, 255)
|
||||||
|
val world = this@DefaultParcelGenerator.world.world
|
||||||
|
val b = getBottomCoord(parcel)
|
||||||
|
val parcelSize = o.parcelSize
|
||||||
|
for (x in b.x until b.x + parcelSize) {
|
||||||
|
for (z in b.z until b.z + parcelSize) {
|
||||||
|
for (y in range) {
|
||||||
|
yield(world.getBlockAt(x, y, z))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package io.dico.parcels2.math
|
||||||
|
|
||||||
|
fun Double.floor(): Int {
|
||||||
|
val down = toInt()
|
||||||
|
if (down.toDouble() != this && (java.lang.Double.doubleToRawLongBits(this).ushr(63).toInt()) == 1) {
|
||||||
|
return down-1
|
||||||
|
}
|
||||||
|
return down
|
||||||
|
}
|
||||||
|
|
||||||
|
infix fun Int.umod(divisor: Int): Int {
|
||||||
|
val out = this % divisor
|
||||||
|
if (out < 0) {
|
||||||
|
return out + divisor
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
val Int.even: Boolean get() = and(1) == 0
|
||||||
|
|
||||||
|
fun IntRange.clamp(min: Int, max: Int): IntRange {
|
||||||
|
if (first < min) {
|
||||||
|
if (last > max) {
|
||||||
|
return IntRange(min, max)
|
||||||
|
}
|
||||||
|
return IntRange(min, last)
|
||||||
|
}
|
||||||
|
if (last > max) {
|
||||||
|
return IntRange(first, max)
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package io.dico.parcels2.storage
|
||||||
|
|
||||||
|
import io.dico.parcels2.Parcel
|
||||||
|
import io.dico.parcels2.ParcelData
|
||||||
|
import io.dico.parcels2.ParcelOwner
|
||||||
|
import io.dico.parcels2.storage.SerializableParcel
|
||||||
|
import kotlinx.coroutines.experimental.channels.ProducerScope
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
interface Backing {
|
||||||
|
|
||||||
|
val name: String
|
||||||
|
|
||||||
|
suspend fun init()
|
||||||
|
|
||||||
|
suspend fun shutdown()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This producer function is capable of constantly reading plots from a potentially infinite sequence,
|
||||||
|
* and provide plotdata for it as read from the database.
|
||||||
|
*/
|
||||||
|
|
||||||
|
suspend fun ProducerScope<Pair<Parcel, ParcelData?>>.produceParcelData(parcels: Sequence<Parcel>)
|
||||||
|
|
||||||
|
suspend fun readParcelData(plotFor: Parcel): ParcelData?
|
||||||
|
|
||||||
|
suspend fun getOwnedParcels(user: ParcelOwner): List<SerializableParcel>
|
||||||
|
|
||||||
|
suspend fun setParcelOwner(plotFor: Parcel, owner: ParcelOwner?)
|
||||||
|
|
||||||
|
suspend fun setParcelPlayerState(plotFor: Parcel, player: UUID, state: Boolean?)
|
||||||
|
|
||||||
|
suspend fun setParcelAllowsInteractInventory(plot: Parcel, value: Boolean)
|
||||||
|
|
||||||
|
suspend fun setParcelAllowsInteractInputs(plot: Parcel, value: Boolean)
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package io.dico.parcels2.storage
|
||||||
|
|
||||||
|
import com.zaxxer.hikari.HikariDataSource
|
||||||
|
import io.dico.parcels2.Parcel
|
||||||
|
import io.dico.parcels2.ParcelData
|
||||||
|
import io.dico.parcels2.ParcelOwner
|
||||||
|
import kotlinx.coroutines.experimental.channels.ProducerScope
|
||||||
|
import org.jetbrains.exposed.sql.Database
|
||||||
|
import org.jetbrains.exposed.sql.ReferenceOption
|
||||||
|
import org.jetbrains.exposed.sql.Table
|
||||||
|
import java.util.*
|
||||||
|
import javax.sql.DataSource
|
||||||
|
|
||||||
|
object ParcelsTable : Table() {
|
||||||
|
val id = integer("id").autoIncrement().primaryKey()
|
||||||
|
val px = integer("px")
|
||||||
|
val pz = integer("pz")
|
||||||
|
val world_uuid = binary("world_uuid", 16).also { uniqueIndex("location", it, px, pz) }
|
||||||
|
val world = varchar("world", 32).nullable()
|
||||||
|
val owner_uuid = binary("owner_uuid", 16).nullable()
|
||||||
|
val owner = varchar("owner", 16).nullable()
|
||||||
|
}
|
||||||
|
|
||||||
|
object ParcelsAddedTable : Table() {
|
||||||
|
val id = integer("id").references(ParcelsTable.id, ReferenceOption.CASCADE)
|
||||||
|
val player_uuid = binary("player_uuid", 16).also { uniqueIndex("pair", id, it) }
|
||||||
|
val allowed_flag = bool("allowed_flag")
|
||||||
|
}
|
||||||
|
|
||||||
|
object PlayerAddedTable : Table() {
|
||||||
|
val owner_uuid = binary("owner_uuid", 16)
|
||||||
|
val player_uuid = binary("player_uuid", 16).also { uniqueIndex("pair", owner_uuid, it) }
|
||||||
|
val allowed_flag = bool("allowed_flag")
|
||||||
|
}
|
||||||
|
|
||||||
|
class ExposedBacking(val dataSource: DataSource) : Backing {
|
||||||
|
override val name get() = "Exposed"
|
||||||
|
lateinit var database: Database
|
||||||
|
|
||||||
|
override suspend fun init() {
|
||||||
|
database = Database.connect(dataSource)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun shutdown() {
|
||||||
|
if (dataSource is HikariDataSource) {
|
||||||
|
dataSource.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun ProducerScope<Pair<Parcel, ParcelData?>>.produceParcelData(parcels: Sequence<Parcel>) {
|
||||||
|
TODO()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun readParcelData(plotFor: Parcel): ParcelData? {
|
||||||
|
TODO()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getOwnedParcels(user: ParcelOwner): List<SerializableParcel> {
|
||||||
|
TODO()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun setParcelOwner(plotFor: Parcel, owner: ParcelOwner?) {
|
||||||
|
TODO()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun setParcelPlayerState(plotFor: Parcel, player: UUID, state: Boolean?) {
|
||||||
|
TODO()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun setParcelAllowsInteractInventory(plot: Parcel, value: Boolean) {
|
||||||
|
TODO()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun setParcelAllowsInteractInputs(plot: Parcel, value: Boolean) {
|
||||||
|
TODO()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package io.dico.parcels2.storage
|
||||||
|
|
||||||
|
import com.zaxxer.hikari.HikariConfig
|
||||||
|
import com.zaxxer.hikari.HikariDataSource
|
||||||
|
import io.dico.parcels2.DataConnectionOptions
|
||||||
|
import javax.sql.DataSource
|
||||||
|
|
||||||
|
fun getHikariDataSource(dialectName: String,
|
||||||
|
driver: String,
|
||||||
|
dco: DataConnectionOptions): DataSource = with(HikariConfig()) {
|
||||||
|
|
||||||
|
val (address, port) = dco.splitAddressAndPort() ?: throw IllegalArgumentException("Invalid address: ${dco.address}")
|
||||||
|
|
||||||
|
poolName = "redstonerplots"
|
||||||
|
maximumPoolSize = dco.poolSize
|
||||||
|
dataSourceClassName = driver
|
||||||
|
username = dco.username
|
||||||
|
password = dco.password
|
||||||
|
connectionTimeout = 15000
|
||||||
|
leakDetectionThreshold = 10000
|
||||||
|
connectionTestQuery = "SELECT 1"
|
||||||
|
|
||||||
|
addDataSourceProperty("serverName", address)
|
||||||
|
addDataSourceProperty("port", port.toString())
|
||||||
|
addDataSourceProperty("databaseName", dco.database)
|
||||||
|
|
||||||
|
// copied from github.com/lucko/LuckPerms
|
||||||
|
if (dialectName.toLowerCase() == "mariadb") {
|
||||||
|
addDataSourceProperty("properties", "useUnicode=true;characterEncoding=utf8")
|
||||||
|
} else {
|
||||||
|
// doesn't exist on the MariaDB driver
|
||||||
|
addDataSourceProperty("cachePrepStmts", "true")
|
||||||
|
addDataSourceProperty("alwaysSendSetIsolation", "false")
|
||||||
|
addDataSourceProperty("cacheServerConfiguration", "true")
|
||||||
|
addDataSourceProperty("elideSetAutoCommits", "true")
|
||||||
|
addDataSourceProperty("useLocalSessionState", "true")
|
||||||
|
|
||||||
|
// already set as default on mariadb
|
||||||
|
addDataSourceProperty("useServerPrepStmts", "true")
|
||||||
|
addDataSourceProperty("prepStmtCacheSize", "250")
|
||||||
|
addDataSourceProperty("prepStmtCacheSqlLimit", "2048")
|
||||||
|
addDataSourceProperty("cacheCallableStmts", "true")
|
||||||
|
|
||||||
|
// make sure unicode characters can be used.
|
||||||
|
addDataSourceProperty("characterEncoding", "utf8")
|
||||||
|
addDataSourceProperty("useUnicode", "true")
|
||||||
|
}
|
||||||
|
|
||||||
|
HikariDataSource(this)
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package io.dico.parcels2.storage
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonGenerator
|
||||||
|
import com.fasterxml.jackson.core.JsonParser
|
||||||
|
import com.fasterxml.jackson.databind.*
|
||||||
|
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
|
||||||
|
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier
|
||||||
|
import com.fasterxml.jackson.databind.ser.std.StdSerializer
|
||||||
|
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
|
||||||
|
import com.fasterxml.jackson.module.kotlin.KotlinModule
|
||||||
|
import io.dico.parcels2.*
|
||||||
|
import org.bukkit.Bukkit
|
||||||
|
import org.bukkit.block.data.BlockData
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
import kotlin.reflect.full.isSuperclassOf
|
||||||
|
|
||||||
|
val yamlObjectMapper = ObjectMapper(YAMLFactory()).apply {
|
||||||
|
propertyNamingStrategy = PropertyNamingStrategy.KEBAB_CASE
|
||||||
|
|
||||||
|
val kotlinModule = KotlinModule()
|
||||||
|
|
||||||
|
with(kotlinModule) {
|
||||||
|
setSerializerModifier(object : BeanSerializerModifier() {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override fun modifySerializer(config: SerializationConfig?, beanDesc: BeanDescription?, serializer: JsonSerializer<*>?): JsonSerializer<*> {
|
||||||
|
if (GeneratorOptions::class.isSuperclassOf(beanDesc?.beanClass?.kotlin as KClass<*>)) {
|
||||||
|
return GeneratorOptionsSerializer(serializer as JsonSerializer<GeneratorOptions>)
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.modifySerializer(config, beanDesc, serializer)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
addSerializer(BlockDataSerializer())
|
||||||
|
addDeserializer(BlockData::class.java, BlockDataDeserializer())
|
||||||
|
|
||||||
|
/*
|
||||||
|
addSerializer(StorageOptionsSerializer())
|
||||||
|
addDeserializer(StorageOptions::class.java, StorageOptionsDeserializer())
|
||||||
|
*/
|
||||||
|
|
||||||
|
addDeserializer(GeneratorOptions::class.java, GeneratorOptionsDeserializer())
|
||||||
|
}
|
||||||
|
|
||||||
|
registerModule(kotlinModule)
|
||||||
|
}
|
||||||
|
|
||||||
|
private class BlockDataSerializer : StdSerializer<BlockData>(BlockData::class.java) {
|
||||||
|
|
||||||
|
override fun serialize(value: BlockData, gen: JsonGenerator, provider: SerializerProvider) {
|
||||||
|
gen.writeString(value.asString)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class BlockDataDeserializer : StdDeserializer<BlockData>(BlockData::class.java) {
|
||||||
|
|
||||||
|
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): BlockData? {
|
||||||
|
try {
|
||||||
|
return Bukkit.createBlockData(p.valueAsString)
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
throw RuntimeException("Exception occurred at ${p.currentLocation}", ex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
class StorageOptionsDeserializer : JsonDeserializer<StorageOptions>() {
|
||||||
|
|
||||||
|
override fun deserialize(p: JsonParser?, ctxt: DeserializationContext?): StorageOptions {
|
||||||
|
val node = p!!.readValueAsTree<JsonNode>()
|
||||||
|
val dialect = node.get("dialect").asText()
|
||||||
|
val optionsNode = node.get("options")
|
||||||
|
val factory = StorageFactory.getFactory(dialect) ?: throw IllegalStateException("Unknown storage dialect: $dialect")
|
||||||
|
val options = p.codec.treeToValue(optionsNode, factory.optionsClass.java)
|
||||||
|
return StorageOptions(dialect, factory, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class StorageOptionsSerializer : StdSerializer<StorageOptions>(StorageOptions::class.java) {
|
||||||
|
|
||||||
|
override fun serialize(value: StorageOptions?, gen: JsonGenerator?, serializers: SerializerProvider?) {
|
||||||
|
with(gen!!) {
|
||||||
|
writeStartObject()
|
||||||
|
writeStringField("dialect", value!!.dialect)
|
||||||
|
writeFieldName("options")
|
||||||
|
writeObject(value.options)
|
||||||
|
writeEndObject()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
*/
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package io.dico.parcels2.storage
|
||||||
|
|
||||||
|
import io.dico.parcels2.Parcel
|
||||||
|
import io.dico.parcels2.ParcelWorld
|
||||||
|
import io.dico.parcels2.math.Vec2i
|
||||||
|
import org.bukkit.Bukkit
|
||||||
|
import org.bukkit.World
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
data class SerializableWorld(val name: String? = null,
|
||||||
|
val uid: UUID? = null) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
uid ?: name ?: throw IllegalArgumentException("uuid and/or name must be present")
|
||||||
|
}
|
||||||
|
|
||||||
|
val world: World? by lazy { uid?.let { Bukkit.getWorld(it) } ?: name?.let { Bukkit.getWorld(it) } }
|
||||||
|
val parcelWorld: ParcelWorld? by lazy { TODO() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used by storage backing options to encompass the location of a parcel
|
||||||
|
*/
|
||||||
|
data class SerializableParcel(val world: SerializableWorld,
|
||||||
|
val coord: Vec2i) {
|
||||||
|
|
||||||
|
val parcel: Parcel? by lazy { TODO() }
|
||||||
|
}
|
||||||
@@ -1,69 +1,65 @@
|
|||||||
package io.dico.parcels2.storage
|
package io.dico.parcels2.storage
|
||||||
|
|
||||||
import kotlinx.coroutines.experimental.CoroutineDispatcher
|
import io.dico.parcels2.Parcel
|
||||||
import kotlinx.coroutines.experimental.CoroutineScope
|
import io.dico.parcels2.ParcelData
|
||||||
import kotlinx.coroutines.experimental.CoroutineStart
|
import io.dico.parcels2.ParcelOwner
|
||||||
import kotlinx.coroutines.experimental.asCoroutineDispatcher
|
import kotlinx.coroutines.experimental.*
|
||||||
import kotlinx.coroutines.experimental.channels.ProducerJob
|
import kotlinx.coroutines.experimental.channels.ReceiveChannel
|
||||||
import kotlinx.coroutines.experimental.channels.produce
|
import kotlinx.coroutines.experimental.channels.produce
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.CompletableFuture
|
|
||||||
import java.util.concurrent.Executor
|
import java.util.concurrent.Executor
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
/*
|
|
||||||
interface Storage {
|
interface Storage {
|
||||||
|
|
||||||
val name: String
|
val name: String
|
||||||
|
|
||||||
val syncDispatcher: CoroutineDispatcher
|
val syncDispatcher: CoroutineDispatcher
|
||||||
|
|
||||||
val asyncDispatcher: CoroutineDispatcher
|
val asyncDispatcher: CoroutineDispatcher
|
||||||
|
|
||||||
fun init(): CompletableFuture<Unit>
|
fun init(): Deferred<Unit>
|
||||||
|
|
||||||
fun shutdown(): CompletableFuture<Unit>
|
fun shutdown(): Deferred<Unit>
|
||||||
|
|
||||||
fun readPlotData(plotFor: Plot): CompletableFuture<PlotData?>
|
fun readParcelData(parcelFor: Parcel): Deferred<ParcelData?>
|
||||||
|
|
||||||
fun readPlotData(plotsFor: Sequence<Plot>, channelCapacity: Int): ProducerJob<Pair<Plot, PlotData?>>
|
fun readParcelData(parcelsFor: Sequence<Parcel>, channelCapacity: Int): ReceiveChannel<Pair<Parcel, ParcelData?>>
|
||||||
|
|
||||||
fun getOwnedPlots(user: PlotOwner): CompletableFuture<List<SerializablePlot>>
|
fun getOwnedParcels(user: ParcelOwner): Deferred<List<SerializableParcel>>
|
||||||
|
|
||||||
fun setPlotOwner(plotFor: Plot, owner: PlotOwner?): CompletableFuture<Unit>
|
fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?): Deferred<Unit>
|
||||||
|
|
||||||
fun setPlotPlayerState(plotFor: Plot, player: UUID, state: Boolean?): CompletableFuture<Unit>
|
fun setParcelPlayerState(parcelFor: Parcel, player: UUID, state: Boolean?): Deferred<Unit>
|
||||||
|
|
||||||
fun setPlotAllowsInteractInventory(plot: Plot, value: Boolean): CompletableFuture<Unit>
|
fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean): Deferred<Unit>
|
||||||
|
|
||||||
fun setPlotAllowsInteractInputs(plot: Plot, value: Boolean): CompletableFuture<Unit>
|
fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean): Deferred<Unit>
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class StorageWithBacking internal constructor(val backing: Backing) : Storage {
|
class StorageWithCoroutineBacking internal constructor(val backing: Backing) : Storage {
|
||||||
override val name get() = backing.name
|
override val name get() = backing.name
|
||||||
override val syncDispatcher = Executor { it.run() }.asCoroutineDispatcher()
|
override val syncDispatcher = Executor { it.run() }.asCoroutineDispatcher()
|
||||||
override val asyncDispatcher = Executors.newFixedThreadPool(4) { Thread(it, "AbstractStorageThread") }.asCoroutineDispatcher()
|
val poolSize: Int get() = 4
|
||||||
|
override val asyncDispatcher = Executors.newFixedThreadPool(poolSize) { Thread(it, "Parcels2_StorageThread") }.asCoroutineDispatcher()
|
||||||
|
|
||||||
private fun <T> future(block: suspend CoroutineScope.() -> T) = kotlinx.coroutines.experimental.future.future(asyncDispatcher, CoroutineStart.ATOMIC, block)
|
private fun <T> future(block: suspend CoroutineScope.() -> T) = async(context = asyncDispatcher, start = CoroutineStart.ATOMIC, block = block)
|
||||||
|
|
||||||
override fun init(): CompletableFuture<Unit> = future { backing.init() }
|
override fun init() = future { backing.init() }
|
||||||
|
|
||||||
override fun shutdown(): CompletableFuture<Unit> = future { backing.shutdown() }
|
override fun shutdown() = future { backing.shutdown() }
|
||||||
|
|
||||||
override fun readPlotData(plotFor: Plot) = future { backing.readPlotData(plotFor) }
|
override fun readParcelData(parcelFor: Parcel) = future { backing.readParcelData(parcelFor) }
|
||||||
|
|
||||||
override fun readPlotData(plotsFor: Sequence<Plot>, channelCapacity: Int) =
|
override fun readParcelData(parcelsFor: Sequence<Parcel>, channelCapacity: Int) = produce(asyncDispatcher, capacity = channelCapacity) {
|
||||||
produce<Pair<Plot, PlotData?>>(asyncDispatcher, capacity = channelCapacity) { backing.producePlotData(this, plotsFor) }
|
with(backing) { produceParcelData(parcelsFor) }
|
||||||
|
}
|
||||||
|
|
||||||
override fun getOwnedPlots(user: PlotOwner) = future { backing.getOwnedPlots(user) }
|
override fun getOwnedParcels(user: ParcelOwner) = future { backing.getOwnedParcels(user) }
|
||||||
|
|
||||||
override fun setPlotOwner(plotFor: Plot, owner: PlotOwner?) = future { backing.setPlotOwner(plotFor, owner) }
|
override fun setParcelOwner(parcelFor: Parcel, owner: ParcelOwner?) = future { backing.setParcelOwner(parcelFor, owner) }
|
||||||
|
|
||||||
override fun setPlotPlayerState(plotFor: Plot, player: UUID, state: Boolean?) = future { backing.setPlotPlayerState(plotFor, player, state) }
|
override fun setParcelPlayerState(parcelFor: Parcel, player: UUID, state: Boolean?) = future { backing.setParcelPlayerState(parcelFor, player, state) }
|
||||||
|
|
||||||
override fun setPlotAllowsInteractInventory(plot: Plot, value: Boolean) = future { backing.setPlotAllowsInteractInventory(plot, value) }
|
override fun setParcelAllowsInteractInventory(parcel: Parcel, value: Boolean) = future { backing.setParcelAllowsInteractInventory(parcel, value) }
|
||||||
|
|
||||||
override fun setPlotAllowsInteractInputs(plot: Plot, value: Boolean) = future { backing.setPlotAllowsInteractInputs(plot, value) }
|
override fun setParcelAllowsInteractInputs(parcel: Parcel, value: Boolean) = future { backing.setParcelAllowsInteractInputs(parcel, value) }
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package io.dico.parcels2.storage
|
||||||
|
|
||||||
|
import io.dico.parcels2.DataConnectionOptions
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
interface StorageFactory {
|
||||||
|
companion object StorageFactories {
|
||||||
|
private val map: MutableMap<String, StorageFactory> = HashMap()
|
||||||
|
|
||||||
|
fun registerFactory(method: String, generator: StorageFactory): Boolean = map.putIfAbsent(method.toLowerCase(), generator) == null
|
||||||
|
|
||||||
|
fun getFactory(method: String): StorageFactory? = map[method.toLowerCase()]
|
||||||
|
|
||||||
|
init {
|
||||||
|
// have to write the code like this in kotlin.
|
||||||
|
// This code is absolutely disgusting
|
||||||
|
ConnectionStorageFactory().register(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val optionsClass: KClass<out Any>
|
||||||
|
|
||||||
|
fun newStorageInstance(method: String, options: Any): Storage
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConnectionStorageFactory : StorageFactory {
|
||||||
|
override val optionsClass = DataConnectionOptions::class
|
||||||
|
|
||||||
|
private val types: Map<String, String> = with(HashMap<String, String>()) {
|
||||||
|
put("mysql", "com.mysql.jdbc.jdbc2.optional.MysqlDataSource")
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun register(companion: StorageFactory.StorageFactories) {
|
||||||
|
types.keys.forEach {
|
||||||
|
companion.registerFactory(it, this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun newStorageInstance(dialect: String, options: Any): Storage {
|
||||||
|
val driverClass = types[dialect.toLowerCase()] ?: throw IllegalArgumentException("Storage dialect $dialect is not supported")
|
||||||
|
return StorageWithCoroutineBacking(ExposedBacking(getHikariDataSource(dialect, driverClass, options as DataConnectionOptions)))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
package io.dico.parcels2.storage.backing
|
|
||||||
|
|
||||||
import org.jetbrains.exposed.sql.ReferenceOption
|
|
||||||
import org.jetbrains.exposed.sql.Table
|
|
||||||
|
|
||||||
object ParcelsTable : Table() {
|
|
||||||
val id = integer("id").autoIncrement().primaryKey()
|
|
||||||
val px = integer("px")
|
|
||||||
val pz = integer("pz")
|
|
||||||
val world_uuid = binary("world_uuid", 16).also { uniqueIndex("location", it, px, pz) }
|
|
||||||
val world = varchar("world", 32).nullable()
|
|
||||||
val owner_uuid = binary("owner_uuid", 16).nullable()
|
|
||||||
val owner = varchar("owner", 16).nullable()
|
|
||||||
}
|
|
||||||
|
|
||||||
object ParcelsAddedTable : Table() {
|
|
||||||
val id = integer("id").references(ParcelsTable.id, ReferenceOption.CASCADE)
|
|
||||||
val player_uuid = binary("player_uuid", 16).also { uniqueIndex("pair", id, it) }
|
|
||||||
val allowed_flag = bool("allowed_flag")
|
|
||||||
}
|
|
||||||
|
|
||||||
object PlayerAddedTable : Table() {
|
|
||||||
val owner_uuid = binary("owner_uuid", 16)
|
|
||||||
val player_uuid = binary("player_uuid", 16).also { uniqueIndex("pair", owner_uuid, it) }
|
|
||||||
val allowed_flag = bool("allowed_flag")
|
|
||||||
}
|
|
||||||
|
|
||||||
class AbstractParcelsDatabase {
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,13 +1,17 @@
|
|||||||
package io.dico.parcels2.util
|
package io.dico.parcels2.util
|
||||||
|
|
||||||
|
import io.dico.dicore.Formatting
|
||||||
|
import io.dico.parcels2.ParcelsPlugin
|
||||||
|
import io.dico.parcels2.logger
|
||||||
import org.bukkit.entity.Player
|
import org.bukkit.entity.Player
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin
|
||||||
|
|
||||||
val Player.hasBanBypass get() = hasPermission("plots.admin.bypass.ban")
|
inline val Player.hasBanBypass get() = hasPermission("plots.admin.bypass.ban")
|
||||||
val Player.hasBuildAnywhere get() = hasPermission("plots.admin.bypass.build")
|
inline val Player.hasBuildAnywhere get() = hasPermission("plots.admin.bypass.build")
|
||||||
val Player.hasGamemodeBypass get() = hasPermission("plots.admin.bypass.gamemode")
|
inline val Player.hasGamemodeBypass get() = hasPermission("plots.admin.bypass.gamemode")
|
||||||
val Player.hasAdminManage get() = hasPermission("plots.admin.manage")
|
inline val Player.hasAdminManage get() = hasPermission("plots.admin.manage")
|
||||||
val Player.hasPlotHomeOthers get() = hasPermission("plots.command.home.others")
|
inline val Player.hasPlotHomeOthers get() = hasPermission("plots.command.home.others")
|
||||||
val Player.hasRandomSpecific get() = hasPermission("plots.command.random.specific")
|
inline val Player.hasRandomSpecific get() = hasPermission("plots.command.random.specific")
|
||||||
val Player.plotLimit: Int
|
val Player.plotLimit: Int
|
||||||
get() {
|
get() {
|
||||||
for (info in effectivePermissions) {
|
for (info in effectivePermissions) {
|
||||||
@@ -18,15 +22,15 @@ val Player.plotLimit: Int
|
|||||||
return Int.MAX_VALUE
|
return Int.MAX_VALUE
|
||||||
}
|
}
|
||||||
return limitString.toIntOrNull() ?: DEFAULT_LIMIT.also {
|
return limitString.toIntOrNull() ?: DEFAULT_LIMIT.also {
|
||||||
Main.instance.logger.severe("$name has permission '$perm'. The suffix can not be parsed to an integer (or *).")
|
logger.warn("$name has permission '$perm'. The suffix can not be parsed to an integer (or *).")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return DEFAULT_LIMIT
|
return DEFAULT_LIMIT
|
||||||
}
|
}
|
||||||
|
|
||||||
val DEFAULT_LIMIT = 1
|
private const val DEFAULT_LIMIT = 1
|
||||||
internal val prefix = Formatting.translateChars('&', "&4[&c${Main.instance.name}&4] &a")
|
private val prefix = Formatting.translateChars('&', "&4[&c${JavaPlugin.getPlugin(ParcelsPlugin::class.java).name}&4] &a")
|
||||||
|
|
||||||
fun Player.sendPlotMessage(except: Boolean = false, nopermit: Boolean = false, message: String) {
|
fun Player.sendPlotMessage(except: Boolean = false, nopermit: Boolean = false, message: String) {
|
||||||
if (except) {
|
if (except) {
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<configuration>
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{HH:mm:ss.SSS} %magenta(%-8.-8(%thread)) %highlight(%-5level) %boldCyan(%32.-32logger{32}) - %msg%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<root level="info">
|
||||||
|
<appender-ref ref="STDOUT" />
|
||||||
|
</root>
|
||||||
|
</configuration>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
name: Parcels
|
||||||
|
author: Dico
|
||||||
|
main: io.dico.parcels2.ParcelsPlugin
|
||||||
|
version: 0.1
|
||||||
|
api-version: 1.13
|
||||||
Reference in New Issue
Block a user