summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDico200 <dico.karssiens@gmail.com>2018-07-22 03:25:13 +0200
committerDico200 <dico.karssiens@gmail.com>2018-07-22 03:25:13 +0200
commitdbcc90ac8a938e9098dc93206f061cc2a4765ad6 (patch)
tree2d14c4ce997a87303fffb434ef20ab825aa50acc
parent519f3f6b5c39eebace0247b34d56584db463f92b (diff)
Add a few components of dicore3
-rw-r--r--src/main/java/io/dico/dicore/Reflection.java780
-rw-r--r--src/main/java/io/dico/dicore/Registrator.java987
-rw-r--r--src/main/java/io/dico/dicore/exceptions/ExceptionHandler.java203
-rw-r--r--src/main/java/io/dico/dicore/exceptions/checkedfunctions/CheckedBiConsumer.java69
-rw-r--r--src/main/java/io/dico/dicore/exceptions/checkedfunctions/CheckedBiFunction.java76
-rw-r--r--src/main/java/io/dico/dicore/exceptions/checkedfunctions/CheckedConsumer.java67
-rw-r--r--src/main/java/io/dico/dicore/exceptions/checkedfunctions/CheckedFunction.java74
-rw-r--r--src/main/java/io/dico/dicore/exceptions/checkedfunctions/CheckedFunctionalObject.java85
-rw-r--r--src/main/java/io/dico/dicore/exceptions/checkedfunctions/CheckedRunnable.java62
-rw-r--r--src/main/java/io/dico/dicore/exceptions/checkedfunctions/CheckedSupplier.java70
-rw-r--r--src/main/kotlin/io/dico/parcels2/Parcel.kt3
-rw-r--r--src/main/kotlin/io/dico/parcels2/util/PlayerExtensions.kt39
12 files changed, 2514 insertions, 1 deletions
diff --git a/src/main/java/io/dico/dicore/Reflection.java b/src/main/java/io/dico/dicore/Reflection.java
new file mode 100644
index 0000000..b7b1ed4
--- /dev/null
+++ b/src/main/java/io/dico/dicore/Reflection.java
@@ -0,0 +1,780 @@
+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);
+ }
+
+}
diff --git a/src/main/java/io/dico/dicore/Registrator.java b/src/main/java/io/dico/dicore/Registrator.java
new file mode 100644
index 0000000..9977418
--- /dev/null
+++ b/src/main/java/io/dico/dicore/Registrator.java
@@ -0,0 +1,987 @@
+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;
+ }
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/io/dico/dicore/exceptions/ExceptionHandler.java b/src/main/java/io/dico/dicore/exceptions/ExceptionHandler.java
new file mode 100644
index 0000000..002eaa0
--- /dev/null
+++ b/src/main/java/io/dico/dicore/exceptions/ExceptionHandler.java
@@ -0,0 +1,203 @@
+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();
+ }
+ }
+
+}
diff --git a/src/main/java/io/dico/dicore/exceptions/checkedfunctions/CheckedBiConsumer.java b/src/main/java/io/dico/dicore/exceptions/checkedfunctions/CheckedBiConsumer.java
new file mode 100644
index 0000000..badf05b
--- /dev/null
+++ b/src/main/java/io/dico/dicore/exceptions/checkedfunctions/CheckedBiConsumer.java
@@ -0,0 +1,69 @@
+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);
+ }
+ };
+ }
+
+}
diff --git a/src/main/java/io/dico/dicore/exceptions/checkedfunctions/CheckedBiFunction.java b/src/main/java/io/dico/dicore/exceptions/checkedfunctions/CheckedBiFunction.java
new file mode 100644
index 0000000..9b39325
--- /dev/null
+++ b/src/main/java/io/dico/dicore/exceptions/checkedfunctions/CheckedBiFunction.java
@@ -0,0 +1,76 @@
+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);
+ }
+ };
+ }
+}
diff --git a/src/main/java/io/dico/dicore/exceptions/checkedfunctions/CheckedConsumer.java b/src/main/java/io/dico/dicore/exceptions/checkedfunctions/CheckedConsumer.java
new file mode 100644
index 0000000..fa48ae8
--- /dev/null
+++ b/src/main/java/io/dico/dicore/exceptions/checkedfunctions/CheckedConsumer.java
@@ -0,0 +1,67 @@
+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);
+ }
+ };
+ }
+
+}
diff --git a/src/main/java/io/dico/dicore/exceptions/checkedfunctions/CheckedFunction.java b/src/main/java/io/dico/dicore/exceptions/checkedfunctions/CheckedFunction.java
new file mode 100644
index 0000000..9ff806a
--- /dev/null
+++ b/src/main/java/io/dico/dicore/exceptions/checkedfunctions/CheckedFunction.java
@@ -0,0 +1,74 @@
+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);
+ }
+ };
+ }
+
+}
diff --git a/src/main/java/io/dico/dicore/exceptions/checkedfunctions/CheckedFunctionalObject.java b/src/main/java/io/dico/dicore/exceptions/checkedfunctions/CheckedFunctionalObject.java
new file mode 100644
index 0000000..e84b33d
--- /dev/null
+++ b/src/main/java/io/dico/dicore/exceptions/checkedfunctions/CheckedFunctionalObject.java
@@ -0,0 +1,85 @@
+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);
+
+}
diff --git a/src/main/java/io/dico/dicore/exceptions/checkedfunctions/CheckedRunnable.java b/src/main/java/io/dico/dicore/exceptions/checkedfunctions/CheckedRunnable.java
new file mode 100644
index 0000000..55de6f8
--- /dev/null
+++ b/src/main/java/io/dico/dicore/exceptions/checkedfunctions/CheckedRunnable.java
@@ -0,0 +1,62 @@
+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);
+ }
+ };
+ }
+
+}
diff --git a/src/main/java/io/dico/dicore/exceptions/checkedfunctions/CheckedSupplier.java b/src/main/java/io/dico/dicore/exceptions/checkedfunctions/CheckedSupplier.java
new file mode 100644
index 0000000..dec2e7e
--- /dev/null
+++ b/src/main/java/io/dico/dicore/exceptions/checkedfunctions/CheckedSupplier.java
@@ -0,0 +1,70 @@
+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);
+ }
+ };
+ }
+}
diff --git a/src/main/kotlin/io/dico/parcels2/Parcel.kt b/src/main/kotlin/io/dico/parcels2/Parcel.kt
index 27b9268..6fd8cf3 100644
--- a/src/main/kotlin/io/dico/parcels2/Parcel.kt
+++ b/src/main/kotlin/io/dico/parcels2/Parcel.kt
@@ -8,7 +8,8 @@ import java.util.*
class Parcel(val world: ParcelWorld,
val pos: Vec2i,
- val data: ParcelData = ParcelData()) {
+ var data: ParcelData = ParcelData()) {
+
}
diff --git a/src/main/kotlin/io/dico/parcels2/util/PlayerExtensions.kt b/src/main/kotlin/io/dico/parcels2/util/PlayerExtensions.kt
new file mode 100644
index 0000000..47bbf8c
--- /dev/null
+++ b/src/main/kotlin/io/dico/parcels2/util/PlayerExtensions.kt
@@ -0,0 +1,39 @@
+package io.dico.parcels2.util
+
+import org.bukkit.entity.Player
+
+val Player.hasBanBypass get() = hasPermission("plots.admin.bypass.ban")
+val Player.hasBuildAnywhere get() = hasPermission("plots.admin.bypass.build")
+val Player.hasGamemodeBypass get() = hasPermission("plots.admin.bypass.gamemode")
+val Player.hasAdminManage get() = hasPermission("plots.admin.manage")
+val Player.hasPlotHomeOthers get() = hasPermission("plots.command.home.others")
+val Player.hasRandomSpecific get() = hasPermission("plots.command.random.specific")
+val Player.plotLimit: Int
+ get() {
+ for (info in effectivePermissions) {
+ val perm = info.permission
+ if (perm.startsWith("plots.limit.")) {
+ val limitString = perm.substring("plots.limit.".length)
+ if (limitString == "*") {
+ return Int.MAX_VALUE
+ }
+ return limitString.toIntOrNull() ?: DEFAULT_LIMIT.also {
+ Main.instance.logger.severe("$name has permission '$perm'. The suffix can not be parsed to an integer (or *).")
+ }
+ }
+ }
+ return DEFAULT_LIMIT
+ }
+
+val DEFAULT_LIMIT = 1
+internal val prefix = Formatting.translateChars('&', "&4[&c${Main.instance.name}&4] &a")
+
+fun Player.sendPlotMessage(except: Boolean = false, nopermit: Boolean = false, message: String) {
+ if (except) {
+ sendMessage(prefix + Formatting.YELLOW + Formatting.translateChars('&', message))
+ } else if (nopermit) {
+ sendMessage(prefix + Formatting.RED + Formatting.translateChars('&', message))
+ } else {
+ sendMessage(prefix + Formatting.translateChars('&', message))
+ }
+} \ No newline at end of file