Add dicore3-core, without really irrelevant packages
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -5,5 +5,4 @@ out/
|
|||||||
build/
|
build/
|
||||||
/debug/
|
/debug/
|
||||||
target/
|
target/
|
||||||
/gradle-output.txt
|
/gradle-output.txt
|
||||||
/dicore3/
|
|
||||||
16
dicore3/core/build.gradle.kts
Normal file
16
dicore3/core/build.gradle.kts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
group = "io.dico.dicore3"
|
||||||
|
version = "1.2.5"
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
java
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories() {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compile(files("../../res/spigot-1.13-pre7.jar"))
|
||||||
|
|
||||||
|
}
|
||||||
182
dicore3/core/src/main/java/io/dico/dicore/BitModifier.java
Normal file
182
dicore3/core/src/main/java/io/dico/dicore/BitModifier.java
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
package io.dico.dicore;
|
||||||
|
|
||||||
|
public interface BitModifier {
|
||||||
|
|
||||||
|
long mask();
|
||||||
|
|
||||||
|
long get(long x);
|
||||||
|
|
||||||
|
long set(long x, long value);
|
||||||
|
|
||||||
|
default int lowerBound() {
|
||||||
|
return Long.numberOfTrailingZeros(mask());
|
||||||
|
}
|
||||||
|
|
||||||
|
default int upperBound() {
|
||||||
|
return 64 - Long.numberOfLeadingZeros(mask());
|
||||||
|
}
|
||||||
|
|
||||||
|
default int bitCount() {
|
||||||
|
return Long.bitCount(mask());
|
||||||
|
}
|
||||||
|
|
||||||
|
default boolean getBoolean(long x) {
|
||||||
|
return get(x) == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
default long setBoolean(long x, boolean value) {
|
||||||
|
return set(x, value ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
default int getInt(long x) {
|
||||||
|
return (int) (get(x) & 0xFFFFFFFFL);
|
||||||
|
}
|
||||||
|
|
||||||
|
default long setInt(long x, int value) {
|
||||||
|
return set(x, value & 0xFFFFFFFFL);
|
||||||
|
}
|
||||||
|
|
||||||
|
default short getShort(long x) {
|
||||||
|
return (short) (get(x) & 0xFFFFL);
|
||||||
|
}
|
||||||
|
|
||||||
|
default long setShort(long x, int value) {
|
||||||
|
return set(x, value & 0xFFFFL);
|
||||||
|
}
|
||||||
|
|
||||||
|
default byte getByte(long x) {
|
||||||
|
return (byte) (get(x) & 0xFFL);
|
||||||
|
}
|
||||||
|
|
||||||
|
default long setByte(long x, int value) {
|
||||||
|
return set(x, value & 0xFFL);
|
||||||
|
}
|
||||||
|
|
||||||
|
final class OfSingle implements BitModifier {
|
||||||
|
private final long mask;
|
||||||
|
|
||||||
|
public OfSingle(int bit) {
|
||||||
|
if (bit < 0 || bit >= 64) {
|
||||||
|
throw new IndexOutOfBoundsException();
|
||||||
|
}
|
||||||
|
this.mask = 1L << bit;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int bitCount() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long mask() {
|
||||||
|
return mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getBoolean(long x) {
|
||||||
|
return (x & mask) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long setBoolean(long x, boolean value) {
|
||||||
|
return value ? (x | mask) : (x & ~mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long get(long x) {
|
||||||
|
return getBoolean(x) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long set(long x, long value) {
|
||||||
|
if (value < 0 || value > 1) {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
return setBoolean(x, value == 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class OfMultiple implements BitModifier {
|
||||||
|
private final int lowerBound;
|
||||||
|
private final int bitCount;
|
||||||
|
private final long mask;
|
||||||
|
|
||||||
|
public OfMultiple(int lowerBound, int bitCount) {
|
||||||
|
int upperBound = lowerBound + bitCount;
|
||||||
|
if (lowerBound < 0 || lowerBound >= 64 || upperBound < 1 || upperBound > 64 || upperBound < lowerBound) {
|
||||||
|
throw new IndexOutOfBoundsException();
|
||||||
|
}
|
||||||
|
this.lowerBound = lowerBound;
|
||||||
|
this.bitCount = bitCount;
|
||||||
|
this.mask = (Long.MIN_VALUE >> (bitCount - 1)) >>> (64 - bitCount - lowerBound);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int lowerBound() {
|
||||||
|
return lowerBound;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int bitCount() {
|
||||||
|
return bitCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int upperBound() {
|
||||||
|
return lowerBound + bitCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long mask() {
|
||||||
|
return mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long get(long x) {
|
||||||
|
return (x & mask) >>> lowerBound;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long set(long x, long value) {
|
||||||
|
return (x & ~mask) | ((value << lowerBound) & mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class Builder {
|
||||||
|
int currentIndex;
|
||||||
|
|
||||||
|
public int getCurrentIndex() {
|
||||||
|
return currentIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRemaining() {
|
||||||
|
return 64 - currentIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OfSingle single() {
|
||||||
|
checkAvailable(1);
|
||||||
|
return new OfSingle(currentIndex++);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BitModifier size(int size) {
|
||||||
|
if (size == 1) {
|
||||||
|
return single();
|
||||||
|
}
|
||||||
|
checkAvailable(size);
|
||||||
|
BitModifier result = new OfMultiple(currentIndex, size);
|
||||||
|
currentIndex += size;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BitModifier remaining() {
|
||||||
|
return size(getRemaining());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkAvailable(int size) {
|
||||||
|
if (size <= 0 || currentIndex + size > 64) {
|
||||||
|
throw new IllegalStateException("Exceeding bit count of a long");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
291
dicore3/core/src/main/java/io/dico/dicore/Formatting.java
Normal file
291
dicore3/core/src/main/java/io/dico/dicore/Formatting.java
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2017 ProjectOreville
|
||||||
|
*
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Author(s):
|
||||||
|
* Dico Karssiens
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.dico.dicore;
|
||||||
|
|
||||||
|
import gnu.trove.map.TCharObjectMap;
|
||||||
|
import gnu.trove.map.hash.TCharObjectHashMap;
|
||||||
|
|
||||||
|
public final class Formatting implements CharSequence {
|
||||||
|
public static final char FORMAT_CHAR = '\u00a7';
|
||||||
|
private static final TCharObjectMap<Formatting> singleCharInstances = new TCharObjectHashMap<>(16, .5F, '\0');
|
||||||
|
|
||||||
|
public static final Formatting
|
||||||
|
BLACK = from('0'),
|
||||||
|
DARK_BLUE = from('1'),
|
||||||
|
DARL_GREEN = from('2'),
|
||||||
|
CYAN = from('3'),
|
||||||
|
DARK_RED = from('4'),
|
||||||
|
PURPLE = from('5'),
|
||||||
|
ORANGE = from('6'),
|
||||||
|
GRAY = from('7'),
|
||||||
|
DARK_GRAY = from('8'),
|
||||||
|
BLUE = from('9'),
|
||||||
|
GREEN = from('a'),
|
||||||
|
AQUA = from('b'),
|
||||||
|
RED = from('c'),
|
||||||
|
PINK = from('d'),
|
||||||
|
YELLOW = from('e'),
|
||||||
|
WHITE = from('f'),
|
||||||
|
BOLD = from('l'),
|
||||||
|
STRIKETHROUGH = from('m'),
|
||||||
|
UNDERLINE = from('n'),
|
||||||
|
ITALIC = from('o'),
|
||||||
|
MAGIC = from('k'),
|
||||||
|
RESET = from('r'),
|
||||||
|
EMPTY = from('\0');
|
||||||
|
|
||||||
|
public static String stripAll(String value) {
|
||||||
|
return stripAll(FORMAT_CHAR, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String stripAll(char alternateChar, String value) {
|
||||||
|
int index = value.indexOf(alternateChar);
|
||||||
|
int max;
|
||||||
|
if (index == -1 || index == (max = value.length() - 1)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
int from = 0;
|
||||||
|
do {
|
||||||
|
if (isRecognizedChar(value.charAt(index + 1))) {
|
||||||
|
result.append(value, from, index);
|
||||||
|
from = index + 2;
|
||||||
|
} else {
|
||||||
|
result.append(value, from, from = index + 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
index = value.indexOf(alternateChar, index + 1);
|
||||||
|
} while (index != -1 && index != max && from <= max);
|
||||||
|
|
||||||
|
if (from <= max) {
|
||||||
|
result.append(value, from, value.length());
|
||||||
|
}
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String stripFirst(String value) {
|
||||||
|
return stripFirst(FORMAT_CHAR, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String stripFirst(char alternateChar, String value) {
|
||||||
|
int index = value.indexOf(alternateChar);
|
||||||
|
int max;
|
||||||
|
if (index == -1 || index == (max = value.length() - 1)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder result = new StringBuilder(value.length());
|
||||||
|
int from = 0;
|
||||||
|
if (isRecognizedChar(value.charAt(index + 1))) {
|
||||||
|
result.append(value, from, index);
|
||||||
|
from = index + 2;
|
||||||
|
} else {
|
||||||
|
result.append(value, from, from = index + 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (from < max) {
|
||||||
|
result.append(value, from, value.length());
|
||||||
|
}
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Formatting from(char c) {
|
||||||
|
if (isRecognizedChar(c)) {
|
||||||
|
c = Character.toLowerCase(c);
|
||||||
|
Formatting res = singleCharInstances.get(c);
|
||||||
|
if (res == null) {
|
||||||
|
singleCharInstances.put(c, res = new Formatting(c));
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
return EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Formatting from(String chars) {
|
||||||
|
return chars.length() == 1 ? from(chars.charAt(0)) : getFormats(chars, '\0');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Formatting getFormats(String input) {
|
||||||
|
return getFormats(input, FORMAT_CHAR);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Formatting getFormats(String input, char formatChar) {
|
||||||
|
return getFormats(input, 0, input.length(), formatChar);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Formatting getFormats(String input, int start, int end, char formatChar) {
|
||||||
|
if ((start < 0) || (start > end) || (end > input.length())) {
|
||||||
|
throw new IndexOutOfBoundsException("start " + start + ", end " + end + ", input.length() " + input.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean needsFormatChar = formatChar != '\0';
|
||||||
|
char[] formats = new char[6];
|
||||||
|
// just make sure it's not the same as formatChar
|
||||||
|
char previous = (char) (formatChar + 1);
|
||||||
|
|
||||||
|
for (int i = start; i < end; i++) {
|
||||||
|
char c = input.charAt(i);
|
||||||
|
|
||||||
|
if (previous == formatChar || !needsFormatChar) {
|
||||||
|
if (isColourChar(c) || isResetChar(c)) {
|
||||||
|
formats = new char[6];
|
||||||
|
formats[0] = Character.toLowerCase(c);
|
||||||
|
} else if (isFormatChar(c)) {
|
||||||
|
char format = Character.toLowerCase(c);
|
||||||
|
for (int j = 0; j < 6; j++) {
|
||||||
|
if (formats[j] == '\0') {
|
||||||
|
formats[j] = format;
|
||||||
|
break;
|
||||||
|
} else if (formats[j] == format) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
previous = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
return formats[1] == '\0' ? from(formats[0]) : new Formatting(formats);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String translate(String input) {
|
||||||
|
return translateChars('&', input);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String translateChars(char alternateChar, String input) {
|
||||||
|
return translateFormat(alternateChar, FORMAT_CHAR, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String revert(String input) {
|
||||||
|
return revertChars('&', input);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String revertChars(char alternateChar, String input) {
|
||||||
|
return translateFormat(FORMAT_CHAR, alternateChar, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String translateFormat(char fromChar, char toChar, String input) {
|
||||||
|
if (input == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int n = input.length();
|
||||||
|
if (n < 2) {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
char[] result = null;
|
||||||
|
char previous = input.charAt(0);
|
||||||
|
for (int i = 1; i < n; i++) {
|
||||||
|
char c = input.charAt(i);
|
||||||
|
if (previous == fromChar && isRecognizedChar(c)) {
|
||||||
|
if (result == null) {
|
||||||
|
result = input.toCharArray();
|
||||||
|
}
|
||||||
|
result[i - 1] = toChar;
|
||||||
|
}
|
||||||
|
previous = c;
|
||||||
|
}
|
||||||
|
return result == null ? input : String.valueOf(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void translate(StringBuilder input) {
|
||||||
|
translateChars('&', input);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void translateChars(char alternateChar, StringBuilder input) {
|
||||||
|
translateFormat(alternateChar, FORMAT_CHAR, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void revert(StringBuilder input) {
|
||||||
|
revertChars('&', input);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void revertChars(char alternateChar, StringBuilder input) {
|
||||||
|
translateFormat(FORMAT_CHAR, alternateChar, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void translateFormat(char fromChar, char toChar, StringBuilder input) {
|
||||||
|
if (input == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int n = input.length();
|
||||||
|
if (n < 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
char previous = input.charAt(0);
|
||||||
|
for (int i = 1; i < n; i++) {
|
||||||
|
char c = input.charAt(i);
|
||||||
|
if (previous == fromChar && isRecognizedChar(c)) {
|
||||||
|
input.setCharAt(i -1, toChar);
|
||||||
|
}
|
||||||
|
previous = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isRecognizedChar(char c) {
|
||||||
|
return isColourChar(c) || isFormatChar(c) || isResetChar(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isColourChar(char c) {
|
||||||
|
return "0123456789abcdefABCDEF".indexOf(c) > -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isResetChar(char c) {
|
||||||
|
return c == 'r' || c == 'R';
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isFormatChar(char c) {
|
||||||
|
return "lmnokLMNOK".indexOf(c) > -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final String format;
|
||||||
|
|
||||||
|
private Formatting(char[] formats) {
|
||||||
|
StringBuilder format = new StringBuilder(12);
|
||||||
|
for (char c : formats) {
|
||||||
|
if (c != '\0') {
|
||||||
|
format.append(FORMAT_CHAR).append(c);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.format = format.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Formatting(char c) {
|
||||||
|
this.format = (c != '\0') ? String.valueOf(new char[]{FORMAT_CHAR, c}) : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int length() {
|
||||||
|
return format.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public char charAt(int index) {
|
||||||
|
return format.charAt(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String subSequence(int start, int end) {
|
||||||
|
return format.substring(start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return format;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString(char formatChar) {
|
||||||
|
return format.replace(FORMAT_CHAR, formatChar);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
173
dicore3/core/src/main/java/io/dico/dicore/InterfaceChain.java
Normal file
173
dicore3/core/src/main/java/io/dico/dicore/InterfaceChain.java
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
package io.dico.dicore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A chainable object
|
||||||
|
* <p>
|
||||||
|
* It is not possible to declare another upper bound for type parameter Subtype.
|
||||||
|
* However, it is required that it also extends the type parameter Element.
|
||||||
|
*
|
||||||
|
* @param <Subtype> the interface that is chainable
|
||||||
|
* @param <Element> the element of the chain, this is a supertype of the subtype
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public interface InterfaceChain<Element, Subtype extends InterfaceChain<Element, Subtype>> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns the empty InterfaceChain instance.
|
||||||
|
*
|
||||||
|
* @return the empty InterfaceChain instance
|
||||||
|
*/
|
||||||
|
Subtype getEmptyInstance();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns a InterfaceChain with the last added element detached.
|
||||||
|
* <p>
|
||||||
|
* if this InterfaceChain is the empty instance, the empty instance is returned.
|
||||||
|
*
|
||||||
|
* @return a InterfaceChain with the last added element detached
|
||||||
|
* @implNote for the purpose of lambdas, the default implementation also returns the empty instance.
|
||||||
|
*/
|
||||||
|
default Subtype withoutLastNode() {
|
||||||
|
return getEmptyInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns the element that was inserted as the last element
|
||||||
|
* <p>
|
||||||
|
* For instance, calling this method on the result of calling {@link InterfaceChain#withElement(Object)}
|
||||||
|
* would return the given element.
|
||||||
|
*
|
||||||
|
* @return the element that was inserted as the last element
|
||||||
|
* @implNote for the purpose of lambdas, the default implementation returns this object,
|
||||||
|
* which is required to implement the Element type parameter.
|
||||||
|
*/
|
||||||
|
default Element getDelegateOfLastNode() {
|
||||||
|
//noinspection unchecked
|
||||||
|
return (Element) this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The number of elements chained from this InterfaceChain.
|
||||||
|
* @implNote for the purpose of lambdas, the default implementation returns 1.
|
||||||
|
*/
|
||||||
|
default int getElementCount() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a new InterfaceChain that includes the given Element.
|
||||||
|
* <p>
|
||||||
|
* The default implementation of the Subtype should look like this:
|
||||||
|
* <pre> {@code
|
||||||
|
* if (element == null) {
|
||||||
|
* return this;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* int count = getElementCount() + 1;
|
||||||
|
* return new Subtype() {
|
||||||
|
* \@Override
|
||||||
|
* public void exampleElementMethod() {
|
||||||
|
* try {
|
||||||
|
* Subtype.this.exampleElementMethod();
|
||||||
|
* } finally {
|
||||||
|
* element.exampleElementMethod();
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* \@Override
|
||||||
|
* public Subtype withoutLastNode() {
|
||||||
|
* return Subtype.this;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* \@Override
|
||||||
|
* public Element getDelegateOfLastNode() {
|
||||||
|
* return element;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* \@Override
|
||||||
|
* public int getElementCount() {
|
||||||
|
* return count;
|
||||||
|
* }
|
||||||
|
* };
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param element A new element to insert at the end of this InterfaceChain.
|
||||||
|
* @return a new InterfaceChain that includes the given Element.
|
||||||
|
*/
|
||||||
|
Subtype withElement(Element element);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append each of the elements to this InterfaceChain
|
||||||
|
*
|
||||||
|
* @param elements the elements to append
|
||||||
|
* @return a new InterfaceChain with the elements appended
|
||||||
|
*/
|
||||||
|
default Subtype withElements(Element... elements) {
|
||||||
|
Subtype result = (Subtype) this;
|
||||||
|
for (Element element : elements) {
|
||||||
|
result = result.withElement(element);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Example Subtypes implementation
|
||||||
|
|
||||||
|
public class Subtypes {
|
||||||
|
|
||||||
|
private Subtypes() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Subtype empty = new Subtype() {
|
||||||
|
@Override
|
||||||
|
public void exampleElementMethod() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Subtype withElement(Element other) {
|
||||||
|
return Subtypes.singleton(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getElementCount() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Element getDelegateOfLastNode() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static Subtype empty() {
|
||||||
|
return empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Subtype singleton(Element element) {
|
||||||
|
if (element instanceof Subtype) {
|
||||||
|
return (Subtype) element;
|
||||||
|
}
|
||||||
|
if (element == null) {
|
||||||
|
return empty();
|
||||||
|
}
|
||||||
|
return new Subtype() {
|
||||||
|
@Override
|
||||||
|
public void exampleElementMethod() {
|
||||||
|
element.exampleElementMethod();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Element getDelegateOfLastNode() {
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
package io.dico.dicore;
|
||||||
|
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||||
|
import org.bukkit.inventory.Inventory;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.inventory.PlayerInventory;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class InventoryEventUtil {
|
||||||
|
|
||||||
|
private InventoryEventUtil() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ItemStack getNewItem(InventoryClickEvent event) {
|
||||||
|
Inventory clicked = event.getClickedInventory();
|
||||||
|
switch (event.getAction()) {
|
||||||
|
case SWAP_WITH_CURSOR:
|
||||||
|
case PLACE_ALL:
|
||||||
|
return event.getCursor();
|
||||||
|
case PICKUP_ALL:
|
||||||
|
case HOTBAR_MOVE_AND_READD:
|
||||||
|
case MOVE_TO_OTHER_INVENTORY:
|
||||||
|
case DROP_ALL_SLOT:
|
||||||
|
case COLLECT_TO_CURSOR:
|
||||||
|
return null;
|
||||||
|
case PICKUP_HALF:
|
||||||
|
case PICKUP_SOME:
|
||||||
|
ItemStack item = clicked.getItem(event.getSlot()).clone();
|
||||||
|
item.setAmount(item.getAmount() / 2);
|
||||||
|
return item;
|
||||||
|
case PICKUP_ONE:
|
||||||
|
case DROP_ONE_SLOT:
|
||||||
|
item = clicked.getItem(event.getSlot()).clone();
|
||||||
|
item.setAmount(Math.max(0, item.getAmount() - 1));
|
||||||
|
return item;
|
||||||
|
case PLACE_ONE:
|
||||||
|
item = event.getView().getCursor().clone();
|
||||||
|
item.setAmount(1);
|
||||||
|
return item;
|
||||||
|
case PLACE_SOME:
|
||||||
|
item = event.getView().getCursor().clone();
|
||||||
|
item.setAmount(item.getAmount() / 2);
|
||||||
|
return item;
|
||||||
|
case HOTBAR_SWAP:
|
||||||
|
return event.getView().getBottomInventory().getItem(event.getHotbarButton());
|
||||||
|
default:
|
||||||
|
return clicked.getItem(event.getSlot());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static Map<Integer, ItemStack> deduceChangesIfItemAdded(Inventory inventory, ItemStack added, boolean computeNewItem) {
|
||||||
|
int addedAmount = added.getAmount();
|
||||||
|
Map<Integer, ItemStack> rv = Collections.emptyMap();
|
||||||
|
|
||||||
|
for (int n = inventory.getSize(), i = 0; i < n; i++) {
|
||||||
|
if (addedAmount <= 0) break;
|
||||||
|
|
||||||
|
ItemStack current = inventory.getItem(i);
|
||||||
|
if (current == null || current.getType() == Material.AIR || current.isSimilar(added)) {
|
||||||
|
int count = current == null ? 0 : current.getAmount();
|
||||||
|
int max = (current == null ? added : current).getType().getMaxStackSize();
|
||||||
|
if (count < max) {
|
||||||
|
int diff = max - count;
|
||||||
|
if (diff > addedAmount) {
|
||||||
|
diff = addedAmount;
|
||||||
|
}
|
||||||
|
addedAmount -= diff;
|
||||||
|
|
||||||
|
if (rv.isEmpty()) rv = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
if (computeNewItem) {
|
||||||
|
current = (current == null ? added : current).clone();
|
||||||
|
current.setAmount(count + diff);
|
||||||
|
rv.put(i, current);
|
||||||
|
} else {
|
||||||
|
rv.put(i, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static ItemStack getNewHeldItemIfPickedUp(PlayerInventory inventory, ItemStack added) {
|
||||||
|
int heldItemSlot = inventory.getHeldItemSlot();
|
||||||
|
ItemStack heldItem = inventory.getItem(heldItemSlot);
|
||||||
|
|
||||||
|
if (SpigotUtil.isItemPresent(heldItem) && !added.isSimilar(heldItem)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
int amt = added.getAmount();
|
||||||
|
for (int i = 0; i < heldItemSlot; i++) {
|
||||||
|
ItemStack item = inventory.getItem(i);
|
||||||
|
if (!SpigotUtil.isItemPresent(item)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (item.isSimilar(item)) {
|
||||||
|
amt -= Math.max(0, item.getMaxStackSize() - item.getAmount());
|
||||||
|
if (amt <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
added = added.clone();
|
||||||
|
added.setAmount(amt + Math.max(0, heldItem == null ? 0 : heldItem.getAmount()));
|
||||||
|
return added;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
116
dicore3/core/src/main/java/io/dico/dicore/Logging.java
Normal file
116
dicore3/core/src/main/java/io/dico/dicore/Logging.java
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
package io.dico.dicore;
|
||||||
|
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
public interface Logging {
|
||||||
|
|
||||||
|
void info(Object o);
|
||||||
|
|
||||||
|
void warn(Object o);
|
||||||
|
|
||||||
|
void error(Object o);
|
||||||
|
|
||||||
|
void debug(Object o);
|
||||||
|
|
||||||
|
void setDebugging(boolean debugging);
|
||||||
|
|
||||||
|
boolean isDebugging();
|
||||||
|
|
||||||
|
class RootLogging implements Logging {
|
||||||
|
private final String prefix;
|
||||||
|
private final Logger root;
|
||||||
|
private boolean debugging;
|
||||||
|
|
||||||
|
public RootLogging(String prefix, Logger root, boolean debugging) {
|
||||||
|
this.root = root;
|
||||||
|
this.prefix = prefix;
|
||||||
|
this.debugging = debugging;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void info(Object o) {
|
||||||
|
root.info(prefix(o));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void warn(Object o) {
|
||||||
|
root.warning(prefix(o));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void error(Object o) {
|
||||||
|
root.severe(prefix(o));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void debug(Object o) {
|
||||||
|
if (debugging) {
|
||||||
|
root.info(String.format("[DEBUG] %s", prefix(o)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDebugging() {
|
||||||
|
return debugging;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDebugging(boolean debugging) {
|
||||||
|
this.debugging = debugging;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String prefix(Object o) {
|
||||||
|
return String.format("[%s] %s", prefix, String.valueOf(o));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SubLogging implements Logging {
|
||||||
|
protected String prefix;
|
||||||
|
private final Logging superLogger;
|
||||||
|
private boolean debugging;
|
||||||
|
|
||||||
|
public SubLogging(String prefix, Logging superLogger, boolean debugging) {
|
||||||
|
this.superLogger = superLogger;
|
||||||
|
this.prefix = prefix;
|
||||||
|
this.debugging = debugging;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void info(Object o) {
|
||||||
|
superLogger.info(prefix(o));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void warn(Object o) {
|
||||||
|
superLogger.warn(prefix(o));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void error(Object o) {
|
||||||
|
superLogger.error(prefix(o));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void debug(Object o) {
|
||||||
|
if (debugging) {
|
||||||
|
superLogger.info(String.format("[DEBUG] %s", prefix(o)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDebugging() {
|
||||||
|
return debugging;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDebugging(boolean debugging) {
|
||||||
|
this.debugging = debugging;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String prefix(Object o) {
|
||||||
|
return String.format("[%s] %s", prefix, String.valueOf(o));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
780
dicore3/core/src/main/java/io/dico/dicore/Reflection.java
Normal file
780
dicore3/core/src/main/java/io/dico/dicore/Reflection.java
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
987
dicore3/core/src/main/java/io/dico/dicore/Registrator.java
Normal file
987
dicore3/core/src/main/java/io/dico/dicore/Registrator.java
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package io.dico.dicore;
|
||||||
|
|
||||||
|
import org.bukkit.configuration.ConfigurationSection;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class SetBasedWhitelist implements Whitelist {
|
||||||
|
private final Set set;
|
||||||
|
private final boolean blacklist;
|
||||||
|
|
||||||
|
public SetBasedWhitelist(Object[] array, boolean blacklist) {
|
||||||
|
this(Arrays.asList(array), blacklist);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SetBasedWhitelist(ConfigurationSection section, Function<String, ?> parser) {
|
||||||
|
this(section.getStringList("listed").stream().map(parser).filter(Objects::nonNull).collect(Collectors.toList()),
|
||||||
|
section.getBoolean("blacklist", false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public SetBasedWhitelist(Collection collection, boolean blacklist) {
|
||||||
|
Set set;
|
||||||
|
if (collection.isEmpty()) {
|
||||||
|
set = Collections.emptySet();
|
||||||
|
} else if (collection.iterator().next() instanceof Enum) {
|
||||||
|
set = EnumSet.copyOf(collection);
|
||||||
|
} else if (collection instanceof Set) {
|
||||||
|
set = (Set) collection;
|
||||||
|
} else {
|
||||||
|
set = new HashSet<>(collection);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.set = set;
|
||||||
|
this.blacklist = blacklist;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isWhitelisted(Object o) {
|
||||||
|
return blacklist != set.contains(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return (blacklist ? "Blacklist" : "Whitelist") + "{"
|
||||||
|
+ String.join(", ", (CharSequence[]) set.stream().map(String::valueOf).toArray(String[]::new)) + "}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set getSet() {
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBlacklist() {
|
||||||
|
return blacklist;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
408
dicore3/core/src/main/java/io/dico/dicore/SpigotUtil.java
Normal file
408
dicore3/core/src/main/java/io/dico/dicore/SpigotUtil.java
Normal file
@@ -0,0 +1,408 @@
|
|||||||
|
package io.dico.dicore;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.block.Block;
|
||||||
|
import org.bukkit.block.BlockFace;
|
||||||
|
import org.bukkit.block.BlockState;
|
||||||
|
import org.bukkit.configuration.serialization.ConfigurationSerializable;
|
||||||
|
import org.bukkit.entity.*;
|
||||||
|
import org.bukkit.inventory.Inventory;
|
||||||
|
import org.bukkit.inventory.InventoryHolder;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.material.Attachable;
|
||||||
|
import org.bukkit.material.MaterialData;
|
||||||
|
import org.bukkit.projectiles.ProjectileSource;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
public class SpigotUtil {
|
||||||
|
|
||||||
|
private SpigotUtil() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static World matchWorld(String input) {
|
||||||
|
try {
|
||||||
|
UUID uid = UUID.fromString(input);
|
||||||
|
World world = Bukkit.getWorld(uid);
|
||||||
|
if (world != null) {
|
||||||
|
return world;
|
||||||
|
}
|
||||||
|
} catch (IllegalArgumentException ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
|
World result = Bukkit.getWorld(input);
|
||||||
|
if (result == null) {
|
||||||
|
input = input.toLowerCase().replace("_", "").replaceAll("[-_]", "");
|
||||||
|
for (World world : Bukkit.getWorlds()) {
|
||||||
|
if (world.getName().toLowerCase().equals(input)) {
|
||||||
|
result = world;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Block getSupportingBlock(Block block) {
|
||||||
|
MaterialData data = block.getState().getData();
|
||||||
|
if (data instanceof Attachable) {
|
||||||
|
BlockFace attachedOn = ((Attachable) data).getAttachedFace();
|
||||||
|
return block.getRelative(attachedOn);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isItemPresent(ItemStack stack) {
|
||||||
|
return stack != null && stack.getType() != Material.AIR && stack.getAmount() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean removeItems(Inventory from, ItemStack item, int amount) {
|
||||||
|
for (Map.Entry<Integer, ? extends ItemStack> entry : from.all(item.getType()).entrySet()) {
|
||||||
|
ItemStack stack = entry.getValue();
|
||||||
|
if (item.isSimilar(stack)) {
|
||||||
|
amount -= stack.getAmount();
|
||||||
|
int stackAmount = -Math.min(0, amount);
|
||||||
|
if (stackAmount == 0) {
|
||||||
|
from.setItem(entry.getKey(), null);
|
||||||
|
} else {
|
||||||
|
stack.setAmount(stackAmount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return amount <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BlockFace yawToFace(float yaw) {
|
||||||
|
if ((yaw %= 360) < 0)
|
||||||
|
yaw += 360;
|
||||||
|
if (45 <= yaw && yaw < 135)
|
||||||
|
return BlockFace.WEST;
|
||||||
|
if (135 <= yaw && yaw < 225)
|
||||||
|
return BlockFace.NORTH;
|
||||||
|
if (225 <= yaw && yaw < 315)
|
||||||
|
return BlockFace.EAST;
|
||||||
|
return BlockFace.SOUTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void addItems(InventoryHolder entity, ItemStack... items) {
|
||||||
|
Location dropLocation;
|
||||||
|
if (entity instanceof Entity) {
|
||||||
|
dropLocation = ((Entity) entity).getLocation();
|
||||||
|
} else if (entity instanceof BlockState) {
|
||||||
|
dropLocation = ((BlockState) entity).getLocation().add(0.5, 1, 0.5);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Can't find location of this InventoryHolder: " + entity);
|
||||||
|
}
|
||||||
|
World world = dropLocation.getWorld();
|
||||||
|
for (ItemStack toDrop : entity.getInventory().addItem(items).values()) {
|
||||||
|
world.dropItemNaturally(dropLocation, toDrop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String asJsonString(Object object) {
|
||||||
|
return asJsonString(null, object, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String asJsonString(String key, Object object, int indentation) {
|
||||||
|
String indent = new String(new char[indentation * 2]).replace('\0', ' ');
|
||||||
|
StringBuilder builder = new StringBuilder(indent);
|
||||||
|
if (key != null) {
|
||||||
|
builder.append(key).append(": ");
|
||||||
|
}
|
||||||
|
if (object instanceof ConfigurationSerializable) {
|
||||||
|
object = ((ConfigurationSerializable) object).serialize();
|
||||||
|
}
|
||||||
|
if (object instanceof Map) {
|
||||||
|
builder.append("{\n");
|
||||||
|
Map<?, ?> map = (Map) object;
|
||||||
|
for (Map.Entry entry : map.entrySet()) {
|
||||||
|
builder.append(asJsonString(String.valueOf(entry.getKey()), entry.getValue(), indentation + 1));
|
||||||
|
}
|
||||||
|
builder.append(indent).append("}");
|
||||||
|
} else if (object instanceof List) {
|
||||||
|
builder.append("[\n");
|
||||||
|
List list = (List) object;
|
||||||
|
for (Object entry : list) {
|
||||||
|
builder.append(asJsonString(null, entry, indentation + 1));
|
||||||
|
}
|
||||||
|
builder.append(indent).append("]");
|
||||||
|
} else {
|
||||||
|
builder.append(String.valueOf(object));
|
||||||
|
}
|
||||||
|
return builder.append(",\n").toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String asJsonString(String key, Object object, int indentation, BiConsumer<List<?>, StringBuilder> listHeader) {
|
||||||
|
String indent = new String(new char[indentation * 2]).replace('\0', ' ');
|
||||||
|
StringBuilder builder = new StringBuilder(indent);
|
||||||
|
if (key != null) {
|
||||||
|
builder.append(key).append(": ");
|
||||||
|
}
|
||||||
|
if (object instanceof ConfigurationSerializable) {
|
||||||
|
object = ((ConfigurationSerializable) object).serialize();
|
||||||
|
}
|
||||||
|
if (object instanceof Map) {
|
||||||
|
builder.append("{\n");
|
||||||
|
Map<?, ?> map = (Map) object;
|
||||||
|
for (Map.Entry entry : map.entrySet()) {
|
||||||
|
builder.append(asJsonString(String.valueOf(entry.getKey()), entry.getValue(), indentation + 1, listHeader));
|
||||||
|
}
|
||||||
|
builder.append(indent).append("}");
|
||||||
|
} else if (object instanceof List) {
|
||||||
|
builder.append("[");
|
||||||
|
List list = (List) object;
|
||||||
|
listHeader.accept(list, builder);
|
||||||
|
builder.append("\n");
|
||||||
|
for (Object entry : list) {
|
||||||
|
builder.append(asJsonString(null, entry, indentation + 1, listHeader));
|
||||||
|
}
|
||||||
|
builder.append(indent).append("]");
|
||||||
|
} else {
|
||||||
|
builder.append(String.valueOf(object));
|
||||||
|
}
|
||||||
|
return builder.append(",\n").toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BlockFace estimateDirectionTo(Location from, Location to) {
|
||||||
|
double dx = from.getX() - to.getX();
|
||||||
|
double dz = from.getZ() - to.getZ();
|
||||||
|
|
||||||
|
boolean xGreater = Math.abs(dx) - Math.abs(dz) > 0;
|
||||||
|
double f = xGreater ? 2 / Math.abs(dx) : 2 / Math.abs(dz);
|
||||||
|
dx *= f;
|
||||||
|
dz *= f;
|
||||||
|
|
||||||
|
double other = Math.abs(xGreater ? dz : dx);
|
||||||
|
|
||||||
|
if (other <= .5) {
|
||||||
|
return xGreater ? (dx < 0 ? BlockFace.WEST : BlockFace.EAST) : (dz < 0 ? BlockFace.NORTH : BlockFace.SOUTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (other < 1.5) {
|
||||||
|
if (xGreater) {
|
||||||
|
return dx < 0 ? (dz < 0 ? BlockFace.WEST_NORTH_WEST : BlockFace.WEST_SOUTH_WEST) : (dz < 0 ? BlockFace.EAST_NORTH_EAST : BlockFace.EAST_SOUTH_EAST);
|
||||||
|
}
|
||||||
|
return dx < 0 ? (dz < 0 ? BlockFace.NORTH_NORTH_WEST : BlockFace.SOUTH_SOUTH_WEST) : (dz < 0 ? BlockFace.NORTH_NORTH_EAST : BlockFace.SOUTH_SOUTH_EAST);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dx < 0 ? (dz < 0 ? BlockFace.NORTH_WEST : BlockFace.SOUTH_WEST) : (dz < 0 ? BlockFace.NORTH_EAST : BlockFace.SOUTH_EAST);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Entity findEntityFromDamager(Entity damager, EntityType searched) {
|
||||||
|
if (damager.getType() == searched) {
|
||||||
|
return damager;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (damager instanceof Projectile) {
|
||||||
|
ProjectileSource shooter = ((Projectile) damager).getShooter();
|
||||||
|
if (shooter instanceof Entity && ((Entity) shooter).getType() == searched) {
|
||||||
|
return (Entity) shooter;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (damager.getType() == EntityType.PRIMED_TNT) {
|
||||||
|
Entity source = ((TNTPrimed) damager).getSource();
|
||||||
|
if (source.getType() == searched) {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int xpForNextLevel(int currentLevel) {
|
||||||
|
if (currentLevel >= 30) {
|
||||||
|
return 112 + (currentLevel - 30) * 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentLevel >= 15) {
|
||||||
|
return 37 + (currentLevel - 15) * 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 7 + currentLevel * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int removeExp(Player entity, int xp) {
|
||||||
|
int total = entity.getTotalExperience();
|
||||||
|
if (xp > total) {
|
||||||
|
xp = total;
|
||||||
|
}
|
||||||
|
|
||||||
|
int level = entity.getLevel();
|
||||||
|
if (level < 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int removed = 0;
|
||||||
|
int xpForNextLevel = xpForNextLevel(level);
|
||||||
|
int current = (int) entity.getExp() * xpForNextLevel;
|
||||||
|
|
||||||
|
if (xp > current) {
|
||||||
|
xp -= current;
|
||||||
|
total -= current;
|
||||||
|
removed += current;
|
||||||
|
|
||||||
|
if (level == 0) {
|
||||||
|
entity.setExp(0F);
|
||||||
|
entity.setTotalExperience(total);
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
current -= xp;
|
||||||
|
total -= xp;
|
||||||
|
removed += xp;
|
||||||
|
|
||||||
|
entity.setExp((float) current / xpForNextLevel);
|
||||||
|
entity.setTotalExperience(total);
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
xpForNextLevel = xpForNextLevel(--level);
|
||||||
|
if (xpForNextLevel >= xp) {
|
||||||
|
total -= xp;
|
||||||
|
removed += xp;
|
||||||
|
|
||||||
|
entity.setExp(1F / xpForNextLevel * (xpForNextLevel - xp));
|
||||||
|
entity.setTotalExperience(total);
|
||||||
|
entity.setLevel(level);
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
|
||||||
|
xp -= xpForNextLevel;
|
||||||
|
total -= xpForNextLevel;
|
||||||
|
removed += xpForNextLevel;
|
||||||
|
} while (level > 0);
|
||||||
|
|
||||||
|
entity.setExp(0F);
|
||||||
|
entity.setTotalExperience(0);
|
||||||
|
entity.setLevel(0);
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getTotalExp(Player entity) {
|
||||||
|
int rv = 0;
|
||||||
|
int level = Math.min(entity.getLevel(), 20000);
|
||||||
|
for (int i = 0; i < level; i++) {
|
||||||
|
rv += xpForNextLevel(i);
|
||||||
|
}
|
||||||
|
rv += Math.min(1F, Math.max(0F, entity.getExp())) * xpForNextLevel(level);
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double getTotalExpLevels(Player entity) {
|
||||||
|
int xp = entity.getTotalExperience();
|
||||||
|
|
||||||
|
int level = 0;
|
||||||
|
while (xp > 0 && level < 20000) {
|
||||||
|
int needed = xpForNextLevel(level);
|
||||||
|
if (needed > xp) {
|
||||||
|
return level + ((double) xp / needed);
|
||||||
|
}
|
||||||
|
xp -= needed;
|
||||||
|
level++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return level;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getNearbyPlayerCount(Player origin, double range, Predicate<Player> predicate) {
|
||||||
|
List<Entity> entities = origin.getNearbyEntities(range, range, range);
|
||||||
|
int result = 0;
|
||||||
|
for (Entity entity : entities) {
|
||||||
|
if (entity.getType() == EntityType.PLAYER && predicate.test((Player) entity)) {
|
||||||
|
result++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void getNearbyPlayers(Player origin, double range, Collection<Player> collection, Predicate<Player> predicate) {
|
||||||
|
List<Entity> entities = origin.getNearbyEntities(range, range, range);
|
||||||
|
for (Entity entity : entities) {
|
||||||
|
if (entity.getType() == EntityType.PLAYER && predicate.test((Player) entity)) {
|
||||||
|
collection.add((Player) entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void forEachNearbyPlayer(Player origin, double range, Consumer<Player> action) {
|
||||||
|
List<Entity> entities = origin.getNearbyEntities(range, range, range);
|
||||||
|
for (Entity entity : entities) {
|
||||||
|
if (entity.getType() == EntityType.PLAYER) {
|
||||||
|
action.accept((Player) entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double distanceSquared(Location first, Location second) {
|
||||||
|
double dx = first.getX() - second.getX();
|
||||||
|
double dy = first.getY() - second.getY();
|
||||||
|
double dz = first.getZ() - second.getZ();
|
||||||
|
|
||||||
|
return dx * dx + dy * dy + dz * dz;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static <T extends Entity> Iterator<T> findNearbyEntities(Entity origin, boolean includeSelf, Predicate<Entity> predicate, double horizontalRange, double verticalRange) {
|
||||||
|
Objects.requireNonNull(origin);
|
||||||
|
return new Iterator<T>() {
|
||||||
|
Entity next;
|
||||||
|
List<Entity> nearby;
|
||||||
|
int index = 0;
|
||||||
|
int size;
|
||||||
|
|
||||||
|
{
|
||||||
|
if (includeSelf) {
|
||||||
|
next = origin;
|
||||||
|
} else {
|
||||||
|
next = findNext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Entity findNext() {
|
||||||
|
if (nearby == null) {
|
||||||
|
nearby = origin.getNearbyEntities(horizontalRange, verticalRange, horizontalRange);
|
||||||
|
size = nearby.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
while (index < size) {
|
||||||
|
Entity e = nearby.get(index++);
|
||||||
|
if (predicate.test(e)) {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return next != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T next() {
|
||||||
|
if (next == null) {
|
||||||
|
throw new NoSuchElementException();
|
||||||
|
}
|
||||||
|
Entity result = next;
|
||||||
|
next = findNext();
|
||||||
|
//noinspection unchecked
|
||||||
|
return (T) result;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
473
dicore3/core/src/main/java/io/dico/dicore/StringUtil.java
Normal file
473
dicore3/core/src/main/java/io/dico/dicore/StringUtil.java
Normal file
@@ -0,0 +1,473 @@
|
|||||||
|
package io.dico.dicore;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
|
public class StringUtil {
|
||||||
|
|
||||||
|
public static String capitalize(String input) {
|
||||||
|
if (input.length() > 0) {
|
||||||
|
char first = input.charAt(0);
|
||||||
|
if (first != (first = Character.toUpperCase(first))) {
|
||||||
|
char[] result = input.toCharArray();
|
||||||
|
result[0] = first;
|
||||||
|
return String.valueOf(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Capitalizes the first character of the string or the first character of each word
|
||||||
|
*
|
||||||
|
* @param input the string to capitalize
|
||||||
|
* @param spaceChar the character separating each word. If @code '\0' is passed, only the first character of
|
||||||
|
* the input is capitalized.
|
||||||
|
* @return the capitalized string
|
||||||
|
*/
|
||||||
|
public static String capitalize(String input, char spaceChar) {
|
||||||
|
if (spaceChar == '\0') {
|
||||||
|
return capitalize(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
char[] result = null;
|
||||||
|
boolean capitalize = true;
|
||||||
|
for (int n = input.length(), i = 0; i < n; i++) {
|
||||||
|
char c = input.charAt(i);
|
||||||
|
if (capitalize && c != (c = Character.toUpperCase(c))) {
|
||||||
|
if (result == null) result = input.toCharArray();
|
||||||
|
result[i] = c;
|
||||||
|
}
|
||||||
|
capitalize = c == spaceChar;
|
||||||
|
}
|
||||||
|
return result != null ? String.valueOf(result) : input;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String capitalize(String input, char spaceChar, char newSpaceChar) {
|
||||||
|
if (newSpaceChar == '\0') {
|
||||||
|
return capitalize(input, spaceChar);
|
||||||
|
}
|
||||||
|
|
||||||
|
char[] result = null;
|
||||||
|
boolean capitalize = true;
|
||||||
|
for (int n = input.length(), i = 0; i < n; i++) {
|
||||||
|
char c = input.charAt(i);
|
||||||
|
if (capitalize && c != (c = Character.toUpperCase(c))) {
|
||||||
|
if (result == null) result = input.toCharArray();
|
||||||
|
result[i] = c;
|
||||||
|
}
|
||||||
|
if (capitalize = c == spaceChar) {
|
||||||
|
if (result == null) result = input.toCharArray();
|
||||||
|
result[i] = newSpaceChar;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result != null ? String.valueOf(result) : input;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a lowercase version of the input with _ replaced with a space.
|
||||||
|
* Mainly used for making names of enum constants readable.
|
||||||
|
*
|
||||||
|
* @param input
|
||||||
|
* @return a humanified version of @code input
|
||||||
|
*/
|
||||||
|
public static String humanify(String input) {
|
||||||
|
return input == null ? null : input.toLowerCase().replace('_', ' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enumerate the given items, separating them by ", " and finally by " and "
|
||||||
|
*
|
||||||
|
* @param words the items to enumerate (it's not really enumerating....)
|
||||||
|
* @return the enumerated string
|
||||||
|
*/
|
||||||
|
public static String enumerate(String... words) {
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
int size = words.length;
|
||||||
|
int secondLastIndex = size - 2;
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
String word = words[i];
|
||||||
|
if (word.isEmpty())
|
||||||
|
continue;
|
||||||
|
result.append(word);
|
||||||
|
if (i < secondLastIndex)
|
||||||
|
result.append(", ");
|
||||||
|
else if (i == secondLastIndex)
|
||||||
|
result.append(" and ");
|
||||||
|
}
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String enumerate(String list, String regex) {
|
||||||
|
return enumerate(list.split(regex));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a formatted string of the length in millis, containing days, hours and minutes.
|
||||||
|
*
|
||||||
|
* @param length The delay in milliseconds
|
||||||
|
* @return the formatted string
|
||||||
|
*/
|
||||||
|
public static String getTimeLength(long length) {
|
||||||
|
int minute = 60000; // in millis
|
||||||
|
int hour = 60 * minute;
|
||||||
|
int day = 24 * hour;
|
||||||
|
|
||||||
|
int minutes = (int) ((length / minute) % 60);
|
||||||
|
int hours = (int) ((length / hour) % 24);
|
||||||
|
int days = (int) (length / day); //returns floor
|
||||||
|
|
||||||
|
String result = ""; // It will be splitted at "|"
|
||||||
|
if (days != 0)
|
||||||
|
result += days + " days|";
|
||||||
|
if (hours != 0)
|
||||||
|
result += hours + " hours|";
|
||||||
|
if (minutes != 0)
|
||||||
|
result += minutes + " minutes|";
|
||||||
|
return enumerate(result.split("\\|"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a formatted String to represent the given time length, in the given units
|
||||||
|
*
|
||||||
|
* @param sourceAmount Amount of delay
|
||||||
|
* @param sourceUnit Unit of delay
|
||||||
|
* @param ifEmpty the String to return if the
|
||||||
|
* @param displayedUnits units displayed
|
||||||
|
* @return the formatted string
|
||||||
|
* @throws IllegalArgumentException if there are no displayed units
|
||||||
|
*/
|
||||||
|
public static String getTimeLength(long sourceAmount, TimeUnit sourceUnit, String ifEmpty, TimeUnit... displayedUnits) {
|
||||||
|
if (displayedUnits.length == 0) {
|
||||||
|
throw new IllegalArgumentException("No displayed units");
|
||||||
|
}
|
||||||
|
Arrays.sort(displayedUnits, Collections.reverseOrder(TimeUnit::compareTo)); // sort by opposite of enum declaration order (largest -> smallest)
|
||||||
|
List<String> segments = new ArrayList<>(displayedUnits.length);
|
||||||
|
for (TimeUnit unit : displayedUnits) {
|
||||||
|
long displayedAmount = unit.convert(sourceAmount, sourceUnit);
|
||||||
|
sourceAmount -= sourceUnit.convert(displayedAmount, unit);
|
||||||
|
if (displayedAmount > 0) {
|
||||||
|
String unitWord = unit.name().toLowerCase(); // plural
|
||||||
|
if (displayedAmount == 1) {
|
||||||
|
unitWord = unitWord.substring(0, unitWord.length() - 1); // remove s at the end
|
||||||
|
}
|
||||||
|
segments.add(displayedAmount + " " + unitWord);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return segments.isEmpty() ? ifEmpty : enumerate(segments.toArray(new String[segments.size()]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the delay represented by a ban-like delay representation, in milliseconds
|
||||||
|
* Example: "5d2h5m3s" for 5 days, 2 hours, 5 minutes and 3 seconds.
|
||||||
|
* <p>
|
||||||
|
* Supported characters are s, m, h, d, w.
|
||||||
|
* Negative numbers are supported.
|
||||||
|
*
|
||||||
|
* @param input The input string
|
||||||
|
* @return The delay in milliseconds
|
||||||
|
* @throws IllegalArgumentException if the input string isn't properly formatted, or any non-digit character isn't recognized (capitals are not recognized).
|
||||||
|
*/
|
||||||
|
public static long getTimeLength(String input) { //if -1: error
|
||||||
|
long count = 0;
|
||||||
|
int i = 0;
|
||||||
|
while (i < input.length()) {
|
||||||
|
int num = 0;
|
||||||
|
char unit = '\0';
|
||||||
|
boolean negate;
|
||||||
|
if (negate = input.charAt(i) == '-') {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
char c = input.charAt(i);
|
||||||
|
int digit = c - '0';
|
||||||
|
if (0 <= digit && digit < 10) {
|
||||||
|
num = 10 * num + digit;
|
||||||
|
} else {
|
||||||
|
unit = c;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while (i < input.length());
|
||||||
|
|
||||||
|
long unitTime = getUnitTime(unit);
|
||||||
|
if (unitTime == -1)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
if (negate) {
|
||||||
|
unitTime = -unitTime;
|
||||||
|
}
|
||||||
|
count += (num * unitTime);
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the time represented by the given unit character in milliseconds.
|
||||||
|
* <p>
|
||||||
|
* 's' -> 1000
|
||||||
|
* 'm' -> 1000 * 60
|
||||||
|
* 'h' -> 1000 * 60 * 60
|
||||||
|
* 'd' -> 1000 * 60 * 60 * 24
|
||||||
|
* 'w' -> 1000 * 60 * 60 * 24 * 7
|
||||||
|
* anything else -> -1
|
||||||
|
*
|
||||||
|
* @param unit The unit character, as shown above
|
||||||
|
* @return the millisecond delay represented by the unit
|
||||||
|
*/
|
||||||
|
public static long getUnitTime(char unit) { //if -1: no value found
|
||||||
|
switch (Character.toLowerCase(unit)) {
|
||||||
|
case 's':
|
||||||
|
return 1000;
|
||||||
|
case 'm':
|
||||||
|
return 1000 * 60;
|
||||||
|
case 'h':
|
||||||
|
return 1000 * 60 * 60;
|
||||||
|
case 'd':
|
||||||
|
return 1000 * 60 * 60 * 24;
|
||||||
|
case 'w':
|
||||||
|
return 1000 * 60 * 60 * 24 * 7;
|
||||||
|
default:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes a binary representation of the value.
|
||||||
|
* The returned representation always displays 64 bits.
|
||||||
|
* Every 8 bits, the digits are seperated by an _
|
||||||
|
* The representation is prefixed by 0b.
|
||||||
|
* <p>
|
||||||
|
* Example: 0b00000000_11111111_00000001_11110000_00001111_11001100_00001111_10111010
|
||||||
|
*
|
||||||
|
* @param entry the value to represent in binary
|
||||||
|
* @return A binary representation of the long value
|
||||||
|
*/
|
||||||
|
public static String toBinaryString(long entry) {
|
||||||
|
String binary = Long.toBinaryString(entry);
|
||||||
|
String binary64 = String.valueOf(new char[64 - binary.length()]).replace('\0', '0') + binary;
|
||||||
|
String withUnderscores = String.join("_", IntStream.range(0, 8).mapToObj(x -> binary64.substring(x * 8, x * 8 + 8)).toArray(String[]::new));
|
||||||
|
return "0b" + withUnderscores;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turns a generic java classname into a name formatted properly to be an enum constant.
|
||||||
|
*
|
||||||
|
* @param name The string value I'd describe as a generic java classname (so we have CapitalCase)
|
||||||
|
* @return An enum constant version of it (ENUM_FORMAT: CAPITAL_CASE)
|
||||||
|
*/
|
||||||
|
public static String toEnumFormat(String name) {
|
||||||
|
StringBuilder result = new StringBuilder(name.length() + 2);
|
||||||
|
|
||||||
|
boolean capital = true;
|
||||||
|
for (int i = 0, n = name.length(); i < n; i++) {
|
||||||
|
char c = name.charAt(i);
|
||||||
|
if (capital) {
|
||||||
|
capital = Character.isUpperCase(c);
|
||||||
|
} else if (Character.isUpperCase(c)) {
|
||||||
|
capital = true;
|
||||||
|
result.append('_');
|
||||||
|
}
|
||||||
|
result.append(capital ? c : Character.toUpperCase(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces any occurrence of toReplace with another string.
|
||||||
|
* Any colours that occured before the occurence of toReplace, are copied to the end of the replacement.
|
||||||
|
*
|
||||||
|
* @param target The String to query
|
||||||
|
* @param toReplace The sequence to replace
|
||||||
|
* @param with the replacing sequence
|
||||||
|
* @return the result
|
||||||
|
*/
|
||||||
|
public static String replaceKeepColours(String target, String toReplace, String with) {
|
||||||
|
int index = -toReplace.length();
|
||||||
|
while ((index = target.indexOf(toReplace, index + toReplace.length())) != -1) {
|
||||||
|
String start = target.substring(0, index);
|
||||||
|
Formatting coloursBefore = Formatting.getFormats(start);
|
||||||
|
String after;
|
||||||
|
try {
|
||||||
|
after = target.substring(index + toReplace.length());
|
||||||
|
} catch (IndexOutOfBoundsException e) {
|
||||||
|
after = "";
|
||||||
|
}
|
||||||
|
target = start + with + coloursBefore + after;
|
||||||
|
}
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String replParam(String target, String param, Object repl) {
|
||||||
|
return replParam(target, param, repl, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String replParams(String target, String[] params, Object[] repls) {
|
||||||
|
return replParams(target, params, repls, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean replParams(String[] target, String[] params, Object[] repls) {
|
||||||
|
return replParams(target, 0, target.length, params, repls);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean replParams(String[] target, int from, int to, String[] params, Object[] repls) {
|
||||||
|
return replParams(target, from, to, params, repls, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean replParams(List<String> target, String[] params, Object[] repls) {
|
||||||
|
return replParams(target, 0, target.size(), params, repls);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean replParams(List<String> target, int from, int to, String[] params, Object[] repls) {
|
||||||
|
return replParams(target, from, to, params, repls, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String replParamAndTranslate(String target, String param, Object repl) {
|
||||||
|
return replParam(target, param, repl, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String replParamsAndTranslate(String target, String[] params, Object[] repls) {
|
||||||
|
return replParams(target, params, repls, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean replParamsAndTranslate(String[] target, String[] params, Object[] repls) {
|
||||||
|
return replParamsAndTranslate(target, 0, target.length, params, repls);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean replParamsAndTranslate(String[] target, int from, int to, String[] params, Object[] repls) {
|
||||||
|
return replParams(target, from, to, params, repls, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean replParamsAndTranslate(List<String> target, String[] params, Object[] repls) {
|
||||||
|
return replParamsAndTranslate(target, 0, target.size(), params, repls);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean replParamsAndTranslate(List<String> target, int from, int to, String[] params, Object[] repls) {
|
||||||
|
return replParams(target, from, to, params, repls, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String replParam(String target, String param, Object replacementObj, boolean translate) {
|
||||||
|
int idx = target.indexOf(param, 0);
|
||||||
|
if (idx == -1) {
|
||||||
|
return translate ? Formatting.translate(target) : target;
|
||||||
|
}
|
||||||
|
|
||||||
|
String rep = replacementObj.toString();
|
||||||
|
StringBuilder builder = new StringBuilder(target);
|
||||||
|
do {
|
||||||
|
builder.replace(idx, idx + param.length(), rep);
|
||||||
|
idx = builder.indexOf(param, idx + rep.length());
|
||||||
|
} while (idx != -1);
|
||||||
|
|
||||||
|
if (translate) {
|
||||||
|
Formatting.translate(builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("StringEquality")
|
||||||
|
private static boolean replParams(String[] target, int from, int to, String[] params, Object[] repls, boolean translate) {
|
||||||
|
if (from < 0 || to < from || to > target.length) {
|
||||||
|
throw new IllegalArgumentException("Invalid from-to for array size " + target.length + ": " + from + "-" + to);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean change = false;
|
||||||
|
for (int i = from; i < to; i++) {
|
||||||
|
String val = target[i];
|
||||||
|
if (val != (val = replParams(val, params, repls, true, translate))) {
|
||||||
|
target[i] = val;
|
||||||
|
change = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return change;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("StringEquality")
|
||||||
|
private static boolean replParams(List<String> target, int from, int to, String[] params, Object[] repls, boolean translate) {
|
||||||
|
if (from < 0 || to < from || to > target.size()) {
|
||||||
|
throw new IllegalArgumentException("Invalid from-to for list size " + target.size() + ": " + from + "-" + to);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean change = false;
|
||||||
|
if (target instanceof RandomAccess) {
|
||||||
|
for (int i = from; i < to; i++) {
|
||||||
|
String val = target.get(i);
|
||||||
|
if (val != (val = replParams(val, params, repls, true, translate))) {
|
||||||
|
target.set(i, val);
|
||||||
|
change = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ListIterator<String> itr = target.listIterator(from);
|
||||||
|
for (int n = to - from, i = 0; i < n && itr.hasNext(); i++) {
|
||||||
|
String val = itr.next();
|
||||||
|
if (val != (val = replParams(val, params, repls, true, translate))) {
|
||||||
|
itr.set(val);
|
||||||
|
change = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return change;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String replParams(String target, String[] params, Object[] repls, boolean updateRepls, boolean translate) {
|
||||||
|
int n = params.length;
|
||||||
|
if (n != repls.length) {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
|
||||||
|
String param = null;
|
||||||
|
int idx = -1;
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < n; i++) {
|
||||||
|
param = params[i];
|
||||||
|
if (param == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
idx = target.indexOf(param, 0);
|
||||||
|
if (idx != -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idx == -1) {
|
||||||
|
return translate ? Formatting.translate(target) : target;
|
||||||
|
}
|
||||||
|
|
||||||
|
String repl = repls[i].toString();
|
||||||
|
if (updateRepls) {
|
||||||
|
repls[i] = repl;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder(target);
|
||||||
|
do {
|
||||||
|
builder.replace(idx, idx + param.length(), repl);
|
||||||
|
idx = builder.indexOf(param, idx + repl.length());
|
||||||
|
} while (idx != -1);
|
||||||
|
|
||||||
|
for (i++; i < n; i++) {
|
||||||
|
param = params[i];
|
||||||
|
if (param == null || (idx = builder.indexOf(param, 0)) == -1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
repl = repls[i].toString();
|
||||||
|
if (updateRepls) {
|
||||||
|
repls[i] = repl;
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
builder.replace(idx, idx + param.length(), repl);
|
||||||
|
idx = builder.indexOf(param, idx + repl.length());
|
||||||
|
} while (idx != -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (translate) {
|
||||||
|
Formatting.translate(builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
91
dicore3/core/src/main/java/io/dico/dicore/Whitelist.java
Normal file
91
dicore3/core/src/main/java/io/dico/dicore/Whitelist.java
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
package io.dico.dicore;
|
||||||
|
|
||||||
|
import org.bukkit.configuration.ConfigurationSection;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Whitelist extends Predicate {
|
||||||
|
|
||||||
|
Whitelist EVERYTHING = item -> true;
|
||||||
|
Whitelist NOTHING = item -> false;
|
||||||
|
Whitelist NOT_NULL = Objects::nonNull;
|
||||||
|
|
||||||
|
static <T> Predicate<T> everythingAsPredicate() {
|
||||||
|
return (Predicate<T>) EVERYTHING;
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T> Predicate<T> nothingAsPredicate() {
|
||||||
|
return (Predicate<T>) NOTHING;
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T> Predicate<T> notNullAsPredicate() {
|
||||||
|
return (Predicate<T>) NOT_NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Whitelist only(Object item) {
|
||||||
|
return item::equals;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Whitelist not(Object item) {
|
||||||
|
return o -> !item.equals(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Whitelist only(Object item1, Object item2) {
|
||||||
|
return item -> item1.equals(item) || item2.equals(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Whitelist not(Object item1, Object item2) {
|
||||||
|
return item -> !(item1.equals(item) || item2.equals(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
static Whitelist only(Object[] objects) {
|
||||||
|
return new SetBasedWhitelist(objects, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Whitelist not(Object[] objects) {
|
||||||
|
return new SetBasedWhitelist(objects, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Whitelist fromConfig(ConfigurationSection section, Function<String, ?> parser) {
|
||||||
|
if (section == null) {
|
||||||
|
return NOTHING;
|
||||||
|
}
|
||||||
|
boolean blacklist = section.getBoolean("blacklist", false);
|
||||||
|
Set list = section.getStringList("listed").stream().map(parser).filter(Objects::nonNull).collect(Collectors.toSet());
|
||||||
|
switch (list.size()) {
|
||||||
|
case 0:
|
||||||
|
return blacklist ? EVERYTHING : NOTHING;
|
||||||
|
case 1: {
|
||||||
|
Iterator iterator = list.iterator();
|
||||||
|
return blacklist ? not(iterator.next()) : only(iterator.next());
|
||||||
|
}
|
||||||
|
case 2: {
|
||||||
|
Iterator iterator = list.iterator();
|
||||||
|
return blacklist ? not(iterator.next(), iterator.next()) : only(iterator.next(), iterator.next());
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
Object item = list.iterator().next();
|
||||||
|
if (item instanceof Enum) {
|
||||||
|
list = EnumSet.copyOf(list);
|
||||||
|
}
|
||||||
|
return new SetBasedWhitelist(list, blacklist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void copyIntoConfig(ConfigurationSection target, Function<Object, String> mapper, boolean blacklist, Object... objects) {
|
||||||
|
target.set("blacklist", blacklist);
|
||||||
|
target.set("listed", Arrays.stream(objects).map(mapper).unordered().distinct().collect(Collectors.toList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean test(Object o) {
|
||||||
|
return isWhitelisted(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isWhitelisted(Object o);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package io.dico.dicore.event;
|
||||||
|
|
||||||
|
import io.dico.dicore.InterfaceChain;
|
||||||
|
|
||||||
|
public interface ChainedListener<T> extends InterfaceChain<SimpleListener<T>, ChainedListener<T>>, SimpleListener<T> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChainedListener<T> getEmptyInstance() {
|
||||||
|
return ChainedListeners.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChainedListener<T> withElement(SimpleListener<T> element) {
|
||||||
|
if (element == null) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
int count = getElementCount() + 1;
|
||||||
|
return new ChainedListener<T>() {
|
||||||
|
@Override
|
||||||
|
public void accept(T event) {
|
||||||
|
try {
|
||||||
|
ChainedListener.this.accept(event);
|
||||||
|
} finally {
|
||||||
|
element.accept(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChainedListener<T> withoutLastNode() {
|
||||||
|
return ChainedListener.this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SimpleListener<T> getDelegateOfLastNode() {
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getElementCount() {
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package io.dico.dicore.event;
|
||||||
|
|
||||||
|
import io.dico.dicore.InterfaceChain;
|
||||||
|
|
||||||
|
public interface ChainedListenerHandle extends InterfaceChain<ListenerHandle, ChainedListenerHandle>, ListenerHandle {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChainedListenerHandle getEmptyInstance() {
|
||||||
|
return ChainedListenerHandles.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChainedListenerHandle withElement(ListenerHandle element) {
|
||||||
|
if (element == null) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
int count = getElementCount() + 1;
|
||||||
|
return new ChainedListenerHandle() {
|
||||||
|
@Override
|
||||||
|
public void register() {
|
||||||
|
try {
|
||||||
|
ChainedListenerHandle.this.register();
|
||||||
|
} finally {
|
||||||
|
element.register();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unregister() {
|
||||||
|
try {
|
||||||
|
ChainedListenerHandle.this.unregister();
|
||||||
|
} finally {
|
||||||
|
element.unregister();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChainedListenerHandle withoutLastNode() {
|
||||||
|
return ChainedListenerHandle.this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ListenerHandle getDelegateOfLastNode() {
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getElementCount() {
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package io.dico.dicore.event;
|
||||||
|
|
||||||
|
public class ChainedListenerHandles {
|
||||||
|
|
||||||
|
private ChainedListenerHandles() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final ChainedListenerHandle empty = new ChainedListenerHandle() {
|
||||||
|
@Override
|
||||||
|
public void register() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unregister() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChainedListenerHandle withElement(ListenerHandle other) {
|
||||||
|
return ChainedListenerHandles.singleton(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getElementCount() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ListenerHandle getDelegateOfLastNode() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static ChainedListenerHandle empty() {
|
||||||
|
return empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ChainedListenerHandle singleton(ListenerHandle element) {
|
||||||
|
if (element instanceof ChainedListenerHandle) {
|
||||||
|
return (ChainedListenerHandle) element;
|
||||||
|
}
|
||||||
|
if (element == null) {
|
||||||
|
return empty();
|
||||||
|
}
|
||||||
|
return new ChainedListenerHandle() {
|
||||||
|
@Override
|
||||||
|
public void register() {
|
||||||
|
element.register();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unregister() {
|
||||||
|
element.unregister();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ListenerHandle getDelegateOfLastNode() {
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package io.dico.dicore.event;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public class ChainedListeners {
|
||||||
|
|
||||||
|
private static final ChainedListener<?> empty = new ChainedListener<Object>() {
|
||||||
|
@Override
|
||||||
|
public void accept(Object event) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChainedListener<Object> withElement(SimpleListener other) {
|
||||||
|
return ChainedListeners.singleton(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getElementCount() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SimpleListener<Object> getDelegateOfLastNode() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private ChainedListeners() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> ChainedListener<T> empty() {
|
||||||
|
return (ChainedListener<T>) empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> ChainedListener<T> singleton(SimpleListener<T> element) {
|
||||||
|
if (element instanceof ChainedListener) {
|
||||||
|
return (ChainedListener<T>) element;
|
||||||
|
}
|
||||||
|
if (element == null) {
|
||||||
|
return empty();
|
||||||
|
}
|
||||||
|
return new ChainedListener<T>() {
|
||||||
|
@Override
|
||||||
|
public void accept(T event) {
|
||||||
|
element.accept(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SimpleListener getDelegateOfLastNode() {
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package io.dico.dicore.event;
|
||||||
|
|
||||||
|
import org.bukkit.event.Cancellable;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public final class HandlerList<T> {
|
||||||
|
private final List<Listener<T>> source = new ArrayList<>();
|
||||||
|
private Listener<T>[] listeners = newArray(0);
|
||||||
|
|
||||||
|
public void refresh() {
|
||||||
|
source.sort(Comparator.comparingInt(l -> l.getPriority().ordinal()));
|
||||||
|
listeners = source.toArray(newArray(source.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static <T> Listener<T>[] newArray(int length) {
|
||||||
|
return new Listener[length];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void register(Listener<T> listener) {
|
||||||
|
if (!source.contains(listener) && source.add(listener)) {
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ListenerHandle getListenerHandle(Listener<T> listener) {
|
||||||
|
return new ListenerHandle() {
|
||||||
|
@Override
|
||||||
|
public void register() {
|
||||||
|
HandlerList.this.register(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unregister() {
|
||||||
|
HandlerList.this.unregister(listener);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void register(EventPriority priority, Consumer<T> listener) {
|
||||||
|
register(new Listener<T>() {
|
||||||
|
@Override
|
||||||
|
public EventPriority getPriority() {
|
||||||
|
return priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void accept(T event) {
|
||||||
|
listener.accept(event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Listener<T>> getRegistrations() {
|
||||||
|
return Collections.unmodifiableList(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unregister(Listener<T> listener) {
|
||||||
|
if (source.remove(listener)) {
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void callEvent(T event) {
|
||||||
|
if (event instanceof Cancellable) {
|
||||||
|
Cancellable c = (Cancellable) event;
|
||||||
|
boolean cancelled = c.isCancelled();
|
||||||
|
for (Listener<T> listener : listeners) {
|
||||||
|
if (listener.listensToCancelledState(cancelled)) {
|
||||||
|
//EnchantsPlugin.getInstance().debug("Listener acceptance: " + listener.getClass().getSimpleName());
|
||||||
|
listener.accept(event);
|
||||||
|
cancelled = c.isCancelled();
|
||||||
|
} /*else {
|
||||||
|
EnchantsPlugin.getInstance().debug("Listener does not listen to cancelled state of " + cancelled + ": " + listener.getClass().getSimpleName());
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (Listener<T> listener : listeners) {
|
||||||
|
//EnchantsPlugin.getInstance().debug("Listener acceptance: " + listener.getClass().getSimpleName());
|
||||||
|
listener.accept(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package io.dico.dicore.event;
|
||||||
|
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
|
||||||
|
public interface Listener<T> {
|
||||||
|
|
||||||
|
default EventPriority getPriority() {
|
||||||
|
return EventPriority.NORMAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
default boolean listensToCancelledState(boolean cancelled) {
|
||||||
|
return !cancelled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void accept(T event);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package io.dico.dicore.event;
|
||||||
|
|
||||||
|
public interface ListenerHandle {
|
||||||
|
|
||||||
|
void register();
|
||||||
|
|
||||||
|
void unregister();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package io.dico.dicore.event;
|
||||||
|
|
||||||
|
public interface SimpleListener<T> {
|
||||||
|
|
||||||
|
void accept(T event);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,203 @@
|
|||||||
|
package io.dico.dicore.exceptions;
|
||||||
|
|
||||||
|
import io.dico.dicore.exceptions.checkedfunctions.CheckedRunnable;
|
||||||
|
import io.dico.dicore.exceptions.checkedfunctions.CheckedSupplier;
|
||||||
|
import io.dico.dicore.exceptions.checkedfunctions.CheckedFunctionalObject;
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
108
dicore3/core/src/main/java/io/dico/dicore/task/BaseTask.java
Normal file
108
dicore3/core/src/main/java/io/dico/dicore/task/BaseTask.java
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
package io.dico.dicore.task;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.plugin.Plugin;
|
||||||
|
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
|
||||||
|
public abstract class BaseTask<T> {
|
||||||
|
private boolean running = false;
|
||||||
|
private Integer taskId = null;
|
||||||
|
private long workTime = 5L;
|
||||||
|
private int workCount;
|
||||||
|
|
||||||
|
public void start(Plugin plugin, int delay, int period, long workTime) {
|
||||||
|
doStartChecks();
|
||||||
|
this.workTime = workTime;
|
||||||
|
workCount = 0;
|
||||||
|
running = true;
|
||||||
|
|
||||||
|
if (delay == -1) {
|
||||||
|
run();
|
||||||
|
if (!running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
delay = period;
|
||||||
|
}
|
||||||
|
|
||||||
|
taskId = plugin.getServer().getScheduler().scheduleSyncRepeatingTask(plugin, this::run, delay, period);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startImmediately(Plugin plugin, int period, long workTime) {
|
||||||
|
start(plugin, -1, period, workTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void doStartChecks() {
|
||||||
|
if (isRunning()) {
|
||||||
|
throw new IllegalStateException("Can't start when already running");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start(Plugin plugin) {
|
||||||
|
start(plugin, -1, 20, 5L);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onFinish(boolean early) {
|
||||||
|
}
|
||||||
|
|
||||||
|
protected long getWorkTime() {
|
||||||
|
return workTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract boolean process(T object);
|
||||||
|
|
||||||
|
private void run() {
|
||||||
|
workCount++;
|
||||||
|
final long stop = System.currentTimeMillis() + getWorkTime();
|
||||||
|
do {
|
||||||
|
if (!processNext()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} while (System.currentTimeMillis() < stop);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTaskId() {
|
||||||
|
return running ? taskId : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWorkCount() {
|
||||||
|
return workCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRunning() {
|
||||||
|
return running;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract T supply() throws NoSuchElementException;
|
||||||
|
|
||||||
|
private void cancelTask(boolean early) {
|
||||||
|
if (taskId != null) {
|
||||||
|
Bukkit.getScheduler().cancelTask(taskId);
|
||||||
|
}
|
||||||
|
running = false;
|
||||||
|
taskId = null;
|
||||||
|
onFinish(early);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean processNext() {
|
||||||
|
T object;
|
||||||
|
try {
|
||||||
|
object = supply();
|
||||||
|
} catch (NoSuchElementException e) {
|
||||||
|
cancelTask(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (process(object)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelTask(true);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
120
dicore3/core/src/main/java/io/dico/dicore/task/IteratorTask.java
Normal file
120
dicore3/core/src/main/java/io/dico/dicore/task/IteratorTask.java
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
package io.dico.dicore.task;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.BiPredicate;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
public abstract class IteratorTask<T> extends BaseTask<T> {
|
||||||
|
|
||||||
|
private Iterator<? extends T> iterator;
|
||||||
|
|
||||||
|
public IteratorTask() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public IteratorTask(Iterable<? extends T> iterable, boolean clone) {
|
||||||
|
refresh(iterable, clone);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IteratorTask(Iterable<? extends T> iterable) {
|
||||||
|
this(iterable, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IteratorTask(Iterator<? extends T> iterator) {
|
||||||
|
refresh(iterator);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doStartChecks() {
|
||||||
|
super.doStartChecks();
|
||||||
|
if (iterator == null) {
|
||||||
|
throw new IllegalStateException("An iterator must be supplied first");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final void refresh(Iterable<? extends T> iterable, boolean clone) {
|
||||||
|
if (clone) {
|
||||||
|
Collection<T> collection;
|
||||||
|
if (!(iterable instanceof Collection)) {
|
||||||
|
collection = new LinkedList<>();
|
||||||
|
for (T next : iterable) {
|
||||||
|
collection.add(next);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
collection = new ArrayList((Collection) iterable);
|
||||||
|
}
|
||||||
|
iterator = collection.iterator();
|
||||||
|
} else {
|
||||||
|
iterator = iterable.iterator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final void refresh(Iterator<? extends T> iterator) {
|
||||||
|
Objects.requireNonNull(iterator);
|
||||||
|
this.iterator = iterator;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected T supply() {
|
||||||
|
return iterator.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void remove() {
|
||||||
|
iterator.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
// One argument: The processed object
|
||||||
|
|
||||||
|
public static <T> IteratorTask<T> create(Iterable<? extends T> iterable, Consumer<T> processor) {
|
||||||
|
return create(iterable, false, processor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> IteratorTask<T> create(Iterable<? extends T> iterable, boolean clone, Consumer<T> processor) {
|
||||||
|
return create(iterable, clone, object -> {
|
||||||
|
processor.accept(object);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> IteratorTask<T> create(Iterable<? extends T> iterable, Predicate<T> processor) {
|
||||||
|
return create(iterable, false, processor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> IteratorTask<T> create(Iterable<? extends T> iterable, boolean clone, Predicate<T> processor) {
|
||||||
|
return new IteratorTask<T>(iterable, clone) {
|
||||||
|
@Override
|
||||||
|
protected boolean process(T object) {
|
||||||
|
return processor.test(object);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Two arguments: the processed object, and a runnable to remove it from the iterator.
|
||||||
|
|
||||||
|
public static <T> IteratorTask<T> create(Iterable<? extends T> iterable, BiConsumer<T, Runnable> processor) {
|
||||||
|
return create(iterable, false, processor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> IteratorTask<T> create(Iterable<? extends T> iterable, boolean clone, BiConsumer<T, Runnable> processor) {
|
||||||
|
return create(iterable, clone, (object, runnable) -> {
|
||||||
|
processor.accept(object, runnable);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> IteratorTask<T> create(Iterable<? extends T> iterable, BiPredicate<T, Runnable> processor) {
|
||||||
|
return create(iterable, false, processor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> IteratorTask<T> create(Iterable<? extends T> iterable, boolean clone, BiPredicate<T, Runnable> processor) {
|
||||||
|
return new IteratorTask<T>(iterable, clone) {
|
||||||
|
@Override
|
||||||
|
protected boolean process(T object) {
|
||||||
|
return processor.test(object, this::remove);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
19
dicore3/core/src/main/java/io/dico/dicore/task/VoidTask.java
Normal file
19
dicore3/core/src/main/java/io/dico/dicore/task/VoidTask.java
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package io.dico.dicore.task;
|
||||||
|
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
|
||||||
|
public abstract class VoidTask extends BaseTask<Void> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected final boolean process(Void object) {
|
||||||
|
return process();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected final Void supply() throws NoSuchElementException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract boolean process();
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user