#1424: Trial changing a small number of inner enums to classes/interfaces to better support custom values
This PR is a subset of the enum PR #931 and is designed as a low impact trial run of the design and backwards compatibility to inform subsequent development. Additional plugin compatibility features may be available by setting `settings.compatibility.enum-compatibility-mode` to `true` in `bukkit.yml`. By: DerFrZocker <derrieple@gmail.com>
This commit is contained in:
@@ -51,11 +51,6 @@ public class FieldRename {
|
||||
};
|
||||
}
|
||||
|
||||
@RerouteStatic("java/lang/Enum")
|
||||
public static <T extends Enum<T>> T valueOf(Class<T> enumClass, String name, @InjectPluginVersion ApiVersion apiVersion) {
|
||||
return Enum.valueOf(enumClass, rename(apiVersion, enumClass.getName().replace('.', '/'), name));
|
||||
}
|
||||
|
||||
@RequireCompatibility("allow-old-keys-in-registry")
|
||||
public static <T extends Keyed> T get(Registry<T> registry, NamespacedKey namespacedKey) {
|
||||
// We don't have version-specific changes, so just use current, and don't inject a version
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package org.bukkit.craftbukkit.legacy.enums;
|
||||
|
||||
/**
|
||||
* A crash dummy to use, instead of the old enums which matured to Abstracthood or Interfacehood and the baby enums which are still growing.
|
||||
*/
|
||||
public enum DummyEnum {
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
package org.bukkit.craftbukkit.legacy.enums;
|
||||
|
||||
import com.google.common.base.Converter;
|
||||
import com.google.common.base.Enums;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Lists;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collector;
|
||||
import java.util.stream.Collectors;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.Registry;
|
||||
import org.bukkit.craftbukkit.legacy.FieldRename;
|
||||
import org.bukkit.craftbukkit.legacy.reroute.DoNotReroute;
|
||||
import org.bukkit.craftbukkit.legacy.reroute.InjectPluginVersion;
|
||||
import org.bukkit.craftbukkit.legacy.reroute.NotInBukkit;
|
||||
import org.bukkit.craftbukkit.legacy.reroute.RequireCompatibility;
|
||||
import org.bukkit.craftbukkit.legacy.reroute.RequirePluginVersion;
|
||||
import org.bukkit.craftbukkit.legacy.reroute.RerouteArgumentType;
|
||||
import org.bukkit.craftbukkit.legacy.reroute.RerouteReturnType;
|
||||
import org.bukkit.craftbukkit.legacy.reroute.RerouteStatic;
|
||||
import org.bukkit.craftbukkit.util.ApiVersion;
|
||||
import org.bukkit.craftbukkit.util.ClassTraverser;
|
||||
import org.bukkit.entity.Cat;
|
||||
import org.bukkit.entity.Frog;
|
||||
import org.bukkit.entity.Villager;
|
||||
import org.bukkit.map.MapCursor;
|
||||
import org.bukkit.util.OldEnum;
|
||||
|
||||
@NotInBukkit
|
||||
@RequireCompatibility("enum-compatibility-mode")
|
||||
@RequirePluginVersion(maxInclusive = "1.20.6")
|
||||
public class EnumEvil {
|
||||
|
||||
private static final Map<Class<?>, LegacyRegistryData> REGISTRIES = new HashMap<>();
|
||||
|
||||
static {
|
||||
// Add Classes which got changed here
|
||||
REGISTRIES.put(Villager.Type.class, new LegacyRegistryData(Registry.VILLAGER_TYPE, Villager.Type::valueOf));
|
||||
REGISTRIES.put(Villager.Profession.class, new LegacyRegistryData(Registry.VILLAGER_PROFESSION, Villager.Profession::valueOf));
|
||||
REGISTRIES.put(Frog.Variant.class, new LegacyRegistryData(Registry.FROG_VARIANT, Frog.Variant::valueOf));
|
||||
REGISTRIES.put(Cat.Type.class, new LegacyRegistryData(Registry.CAT_VARIANT, Cat.Type::valueOf));
|
||||
REGISTRIES.put(MapCursor.Type.class, new LegacyRegistryData(Registry.MAP_DECORATION_TYPE, MapCursor.Type::valueOf));
|
||||
}
|
||||
|
||||
public static LegacyRegistryData getRegistryData(Class<?> clazz) {
|
||||
ClassTraverser it = new ClassTraverser(clazz);
|
||||
LegacyRegistryData registryData;
|
||||
while (it.hasNext()) {
|
||||
registryData = REGISTRIES.get(it.next());
|
||||
if (registryData != null) {
|
||||
return registryData;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@DoNotReroute
|
||||
public static Registry<?> getRegistry(Class<?> clazz) {
|
||||
LegacyRegistryData registryData = getRegistryData(clazz);
|
||||
|
||||
if (registryData != null) {
|
||||
return registryData.registry();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@RerouteStatic("com/google/common/collect/Maps")
|
||||
@RerouteReturnType("java/util/EnumSet")
|
||||
public static ImposterEnumMap newEnumMap(Class<?> objectClass) {
|
||||
return new ImposterEnumMap(objectClass);
|
||||
}
|
||||
|
||||
@RerouteStatic("com/google/common/collect/Maps")
|
||||
@RerouteReturnType("java/util/EnumSet")
|
||||
public static ImposterEnumMap newEnumMap(Map map) {
|
||||
return new ImposterEnumMap(map);
|
||||
}
|
||||
|
||||
@RerouteStatic("com/google/common/collect/Sets")
|
||||
public static Collector<?, ?, ?> toImmutableEnumSet() {
|
||||
return Collectors.toUnmodifiableSet();
|
||||
}
|
||||
|
||||
@RerouteStatic("com/google/common/collect/Sets")
|
||||
@RerouteReturnType("java/util/EnumSet")
|
||||
public static ImposterEnumSet newEnumSet(Iterable<?> iterable, Class<?> clazz) {
|
||||
ImposterEnumSet set = ImposterEnumSet.noneOf(clazz);
|
||||
|
||||
for (Object some : iterable) {
|
||||
set.add(some);
|
||||
}
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
@RerouteStatic("com/google/common/collect/Sets")
|
||||
public static ImmutableSet<?> immutableEnumSet(Iterable<?> iterable) {
|
||||
return ImmutableSet.of(iterable);
|
||||
}
|
||||
|
||||
@RerouteStatic("com/google/common/collect/Sets")
|
||||
public static ImmutableSet<?> immutableEnumSet(@RerouteArgumentType("java/lang/Enum") Object first, @RerouteArgumentType("[java/lang/Enum") Object... rest) {
|
||||
return ImmutableSet.of(first, rest);
|
||||
}
|
||||
|
||||
@RerouteStatic("com/google/common/base/Enums")
|
||||
public static Field getField(@RerouteArgumentType("java/lang/Enum") Object value) {
|
||||
if (value instanceof Enum eValue) {
|
||||
return Enums.getField(eValue);
|
||||
}
|
||||
|
||||
try {
|
||||
return value.getClass().getField(((OldEnum) value).name());
|
||||
} catch (NoSuchFieldException impossible) {
|
||||
throw new AssertionError(impossible);
|
||||
}
|
||||
}
|
||||
|
||||
@RerouteStatic("com/google/common/base/Enums")
|
||||
public static com.google.common.base.Optional getIfPresent(Class clazz, String name, @InjectPluginVersion ApiVersion apiVersion) {
|
||||
if (clazz.isEnum()) {
|
||||
return Enums.getIfPresent(clazz, name);
|
||||
}
|
||||
|
||||
Registry registry = getRegistry(clazz);
|
||||
if (registry == null) {
|
||||
return com.google.common.base.Optional.absent();
|
||||
}
|
||||
|
||||
name = FieldRename.rename(apiVersion, clazz.getName().replace('.', '/'), name);
|
||||
return com.google.common.base.Optional.fromNullable(registry.get(NamespacedKey.fromString(name.toLowerCase(Locale.ROOT))));
|
||||
}
|
||||
|
||||
@RerouteStatic("com/google/common/base/Enums")
|
||||
public static Converter stringConverter(Class clazz, @InjectPluginVersion ApiVersion apiVersion) {
|
||||
if (clazz.isEnum()) {
|
||||
return Enums.stringConverter(clazz);
|
||||
}
|
||||
|
||||
return new StringConverter(apiVersion, clazz);
|
||||
}
|
||||
|
||||
public static Object[] getEnumConstants(Class<?> clazz) {
|
||||
if (clazz.isEnum()) {
|
||||
return clazz.getEnumConstants();
|
||||
}
|
||||
|
||||
Registry<?> registry = getRegistry(clazz);
|
||||
|
||||
if (registry == null) {
|
||||
return clazz.getEnumConstants();
|
||||
}
|
||||
|
||||
// Need to do this in such away to avoid ClassCastException
|
||||
List<?> values = Lists.newArrayList(registry);
|
||||
Object array = Array.newInstance(clazz, values.size());
|
||||
|
||||
for (int i = 0; i < values.size(); i++) {
|
||||
Array.set(array, i, values.get(i));
|
||||
}
|
||||
|
||||
return (Object[]) array;
|
||||
}
|
||||
|
||||
public static String name(@RerouteArgumentType("java/lang/Enum") Object object) {
|
||||
if (object instanceof OldEnum<?>) {
|
||||
return ((OldEnum<?>) object).name();
|
||||
}
|
||||
|
||||
return ((Enum<?>) object).name();
|
||||
}
|
||||
|
||||
public static int compareTo(@RerouteArgumentType("java/lang/Enum") Object object, @RerouteArgumentType("java/lang/Enum") Object other) {
|
||||
if (object instanceof OldEnum<?>) {
|
||||
return ((OldEnum) object).compareTo((OldEnum) other);
|
||||
}
|
||||
|
||||
return ((Enum) object).compareTo((Enum) other);
|
||||
}
|
||||
|
||||
public static Class<?> getDeclaringClass(@RerouteArgumentType("java/lang/Enum") Object object) {
|
||||
Class<?> clazz = object.getClass();
|
||||
Class<?> zuper = clazz.getSuperclass();
|
||||
return (zuper == Enum.class) ? clazz : zuper;
|
||||
}
|
||||
|
||||
public static Optional<Enum.EnumDesc> describeConstable(@RerouteArgumentType("java/lang/Enum") Object object) {
|
||||
return getDeclaringClass(object)
|
||||
.describeConstable()
|
||||
.map(c -> Enum.EnumDesc.of(c, name(object)));
|
||||
}
|
||||
|
||||
@RerouteStatic("java/lang/Enum")
|
||||
@RerouteReturnType("java/lang/Enum")
|
||||
public static Object valueOf(Class enumClass, String name, @InjectPluginVersion ApiVersion apiVersion) {
|
||||
name = FieldRename.rename(apiVersion, enumClass.getName().replace('.', '/'), name);
|
||||
LegacyRegistryData registryData = getRegistryData(enumClass);
|
||||
if (registryData != null) {
|
||||
return registryData.function().apply(name);
|
||||
}
|
||||
|
||||
return Enum.valueOf(enumClass, name);
|
||||
}
|
||||
|
||||
public static String toString(@RerouteArgumentType("java/lang/Enum") Object object) {
|
||||
return object.toString();
|
||||
}
|
||||
|
||||
public static int ordinal(@RerouteArgumentType("java/lang/Enum") Object object) {
|
||||
if (object instanceof OldEnum<?>) {
|
||||
return ((OldEnum<?>) object).ordinal();
|
||||
}
|
||||
|
||||
return ((Enum<?>) object).ordinal();
|
||||
}
|
||||
|
||||
public record LegacyRegistryData(Registry<?> registry, Function<String, ?> function) {
|
||||
}
|
||||
|
||||
private static final class StringConverter<T extends OldEnum<T>> extends Converter<String, T> implements Serializable {
|
||||
|
||||
private final ApiVersion apiVersion;
|
||||
private final Class<T> clazz;
|
||||
private transient LegacyRegistryData registryData;
|
||||
|
||||
StringConverter(ApiVersion apiVersion, Class<T> clazz) {
|
||||
this.apiVersion = apiVersion;
|
||||
this.clazz = clazz;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected T doForward(String value) {
|
||||
if (registryData == null) {
|
||||
registryData = getRegistryData(clazz);
|
||||
}
|
||||
value = FieldRename.rename(apiVersion, clazz.getName().replace('.', '/'), value);
|
||||
return (T) registryData.function().apply(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String doBackward(T enumValue) {
|
||||
return enumValue.name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (object instanceof StringConverter<?> that) {
|
||||
return this.clazz.equals(that.clazz);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return clazz.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Enums.stringConverter(" + clazz.getName() + ".class)";
|
||||
}
|
||||
|
||||
private static final long serialVersionUID = 0L;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package org.bukkit.craftbukkit.legacy.enums;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Collection;
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* The "I can't believe it works" map.
|
||||
* It replaces every EnumMap with the ImposterEnumMap and uses a HashMap instead of an object array.
|
||||
* Used so that plugins which use an EnumMap still work.
|
||||
*/
|
||||
public class ImposterEnumMap extends AbstractMap<Object, Object> {
|
||||
|
||||
private final Class<?> objectClass;
|
||||
private final Map map;
|
||||
|
||||
public ImposterEnumMap(Class<?> objectClass) {
|
||||
this.objectClass = objectClass;
|
||||
this.map = getMap(objectClass);
|
||||
}
|
||||
|
||||
public ImposterEnumMap(EnumMap enumMap) {
|
||||
this.objectClass = DummyEnum.class;
|
||||
this.map = enumMap.clone();
|
||||
}
|
||||
|
||||
public ImposterEnumMap(Map map) {
|
||||
if (map instanceof ImposterEnumMap) {
|
||||
this.objectClass = ((ImposterEnumMap) map).objectClass;
|
||||
this.map = getMap(objectClass);
|
||||
} else {
|
||||
this.objectClass = DummyEnum.class;
|
||||
this.map = new TreeMap();
|
||||
}
|
||||
|
||||
this.map.putAll(map);
|
||||
}
|
||||
|
||||
private static Map getMap(Class<?> objectClass) {
|
||||
// Since we replace every enum map we might also replace some maps which are for real enums.
|
||||
// If this is the case use a EnumMap instead of a HashMap
|
||||
if (objectClass.isEnum()) {
|
||||
return new EnumMap(objectClass);
|
||||
} else {
|
||||
return new HashMap();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return map.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsValue(Object value) {
|
||||
return map.containsValue(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
return map.containsKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object get(Object key) {
|
||||
return map.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object put(Object key, Object value) {
|
||||
typeCheck(key);
|
||||
return map.put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object remove(Object key) {
|
||||
return map.remove(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(Map<? extends Object, ?> m) {
|
||||
if (map instanceof EnumMap<?, ?>) {
|
||||
map.putAll(m);
|
||||
}
|
||||
|
||||
super.putAll(m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
map.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Object> keySet() {
|
||||
return map.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Object> values() {
|
||||
return map.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Entry<Object, Object>> entrySet() {
|
||||
return map.entrySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return map.equals(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return map.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImposterEnumMap clone() {
|
||||
ImposterEnumMap enumMap = new ImposterEnumMap(objectClass);
|
||||
enumMap.putAll(map);
|
||||
return enumMap;
|
||||
}
|
||||
|
||||
private void typeCheck(Object object) {
|
||||
if (objectClass != DummyEnum.class) {
|
||||
if (!objectClass.isAssignableFrom(object.getClass())) {
|
||||
throw new ClassCastException(object.getClass() + " != " + objectClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,346 @@
|
||||
package org.bukkit.craftbukkit.legacy.enums;
|
||||
|
||||
import java.util.AbstractSet;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import org.bukkit.Registry;
|
||||
import org.bukkit.util.OldEnum;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class ImposterEnumSet extends AbstractSet<Object> {
|
||||
|
||||
private final Class<?> objectClass;
|
||||
private final Set set;
|
||||
|
||||
private static Set createSet(Class<?> clazz) {
|
||||
if (clazz.isEnum()) {
|
||||
return EnumSet.noneOf((Class<Enum>) clazz);
|
||||
} else {
|
||||
return new TreeSet();
|
||||
}
|
||||
}
|
||||
|
||||
public static ImposterEnumSet noneOf(Class<?> clazz) {
|
||||
Set set = createSet(clazz);
|
||||
return new ImposterEnumSet(set, clazz);
|
||||
}
|
||||
|
||||
public static ImposterEnumSet allOf(Class<?> clazz) {
|
||||
Set set;
|
||||
if (clazz.isEnum()) {
|
||||
set = EnumSet.allOf((Class<Enum>) clazz);
|
||||
} else {
|
||||
set = new HashSet();
|
||||
Registry registry = EnumEvil.getRegistry(clazz);
|
||||
if (registry == null) {
|
||||
throw new IllegalArgumentException("Class " + clazz + " is not an Enum nor an OldEnum");
|
||||
}
|
||||
|
||||
for (Object object : registry) {
|
||||
set.add(object);
|
||||
}
|
||||
}
|
||||
return new ImposterEnumSet(set, clazz);
|
||||
}
|
||||
|
||||
public static ImposterEnumSet copyOf(Set set) {
|
||||
Class<?> clazz;
|
||||
if (set instanceof ImposterEnumSet imposter) {
|
||||
set = imposter.set;
|
||||
clazz = imposter.objectClass;
|
||||
} else {
|
||||
if (!set.isEmpty()) {
|
||||
clazz = (Class<?>) set.stream()
|
||||
.filter(val -> val != null)
|
||||
.map(val -> val.getClass())
|
||||
.findAny()
|
||||
.orElse(Object.class);
|
||||
} else {
|
||||
clazz = Object.class;
|
||||
}
|
||||
}
|
||||
|
||||
Set newSet = createSet(clazz);
|
||||
newSet.addAll(set);
|
||||
|
||||
return new ImposterEnumSet(newSet, clazz);
|
||||
}
|
||||
|
||||
public static ImposterEnumSet copyOf(Collection collection) {
|
||||
Class<?> clazz;
|
||||
if (collection instanceof ImposterEnumSet imposter) {
|
||||
collection = imposter.set;
|
||||
clazz = imposter.objectClass;
|
||||
} else {
|
||||
if (!collection.isEmpty()) {
|
||||
clazz = (Class<?>) collection.stream()
|
||||
.filter(val -> val != null)
|
||||
.map(val -> val.getClass())
|
||||
.findAny()
|
||||
.orElse(Object.class);
|
||||
} else {
|
||||
clazz = Object.class;
|
||||
}
|
||||
}
|
||||
|
||||
Set newSet = createSet(clazz);
|
||||
newSet.addAll(collection);
|
||||
|
||||
return new ImposterEnumSet(newSet, clazz);
|
||||
}
|
||||
|
||||
public static ImposterEnumSet complementOf(Set set) {
|
||||
Class<?> clazz = null;
|
||||
if (set instanceof ImposterEnumSet imposter) {
|
||||
set = imposter.set;
|
||||
clazz = imposter.objectClass;
|
||||
}
|
||||
|
||||
if (set instanceof EnumSet<?> enumSet) {
|
||||
enumSet = EnumSet.complementOf(enumSet);
|
||||
|
||||
if (clazz != null) {
|
||||
return new ImposterEnumSet(enumSet, clazz);
|
||||
}
|
||||
|
||||
if (!set.isEmpty()) {
|
||||
clazz = (Class<?>) set.stream()
|
||||
.filter(val -> val != null)
|
||||
.map(val -> val.getClass())
|
||||
.findAny()
|
||||
.orElse(Object.class);
|
||||
} else {
|
||||
clazz = (Class<?>) enumSet.stream()
|
||||
.filter(val -> val != null)
|
||||
.map(val -> val.getClass())
|
||||
.map(val -> (Class) val)
|
||||
.findAny()
|
||||
.orElse(Object.class);
|
||||
}
|
||||
|
||||
return new ImposterEnumSet(enumSet, clazz);
|
||||
}
|
||||
|
||||
if (set.isEmpty() && clazz == null) {
|
||||
throw new IllegalStateException("Class is null and set is empty, cannot get class!");
|
||||
}
|
||||
|
||||
if (clazz == null) {
|
||||
clazz = (Class<?>) set.stream()
|
||||
.filter(val -> val != null)
|
||||
.map(val -> val.getClass())
|
||||
.findAny()
|
||||
.orElse(Object.class);
|
||||
}
|
||||
|
||||
Registry registry = EnumEvil.getRegistry(clazz);
|
||||
Set newSet = new HashSet();
|
||||
|
||||
for (Object value : registry) {
|
||||
if (set.contains(value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
newSet.add(value);
|
||||
}
|
||||
|
||||
return new ImposterEnumSet(newSet, clazz);
|
||||
}
|
||||
|
||||
public static ImposterEnumSet of(Object e) {
|
||||
Set set = createSet(e.getClass());
|
||||
set.add(e);
|
||||
|
||||
return new ImposterEnumSet(set, e.getClass());
|
||||
}
|
||||
|
||||
public static ImposterEnumSet of(Object e1, Object e2) {
|
||||
Set set = createSet(e1.getClass());
|
||||
set.add(e1);
|
||||
set.add(e2);
|
||||
|
||||
return new ImposterEnumSet(set, e1.getClass());
|
||||
}
|
||||
|
||||
public static ImposterEnumSet of(Object e1, Object e2, Object e3) {
|
||||
Set set = createSet(e1.getClass());
|
||||
set.add(e1);
|
||||
set.add(e2);
|
||||
set.add(e3);
|
||||
|
||||
return new ImposterEnumSet(set, e1.getClass());
|
||||
}
|
||||
|
||||
|
||||
public static ImposterEnumSet of(Object e1, Object e2, Object e3, Object e4) {
|
||||
Set set = createSet(e1.getClass());
|
||||
set.add(e1);
|
||||
set.add(e2);
|
||||
set.add(e3);
|
||||
set.add(e4);
|
||||
|
||||
return new ImposterEnumSet(set, e1.getClass());
|
||||
}
|
||||
|
||||
|
||||
public static ImposterEnumSet of(Object e1, Object e2, Object e3, Object e4, Object e5) {
|
||||
Set set = createSet(e1.getClass());
|
||||
set.add(e1);
|
||||
set.add(e2);
|
||||
set.add(e3);
|
||||
set.add(e4);
|
||||
set.add(e5);
|
||||
|
||||
return new ImposterEnumSet(set, e1.getClass());
|
||||
}
|
||||
|
||||
|
||||
public static ImposterEnumSet of(Object e, Object... rest) {
|
||||
Set set = createSet(e.getClass());
|
||||
set.add(e);
|
||||
|
||||
Collections.addAll(set, rest);
|
||||
|
||||
return new ImposterEnumSet(set, e.getClass());
|
||||
}
|
||||
|
||||
public static ImposterEnumSet range(Object from, Object to) {
|
||||
Set set;
|
||||
if (from.getClass().isEnum()) {
|
||||
set = EnumSet.range((Enum) from, (Enum) to);
|
||||
} else {
|
||||
set = new HashSet();
|
||||
Registry registry = EnumEvil.getRegistry(from.getClass());
|
||||
for (Object o : registry) {
|
||||
if (((OldEnum) o).ordinal() < ((OldEnum) from).ordinal()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (((OldEnum) o).ordinal() > ((OldEnum) to).ordinal()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
set.add(o);
|
||||
}
|
||||
}
|
||||
|
||||
return new ImposterEnumSet(set, from.getClass());
|
||||
}
|
||||
|
||||
private ImposterEnumSet(Set set, Class<?> objectClass) {
|
||||
this.set = set;
|
||||
this.objectClass = objectClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Object> iterator() {
|
||||
return set.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return set.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return set.equals(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return set.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> c) {
|
||||
return set.removeAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return set.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
return set.contains(o);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
return set.toArray();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public <T> T[] toArray(@NotNull T[] a) {
|
||||
return (T[]) set.toArray(a);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(Object o) {
|
||||
typeCheck(o);
|
||||
return set.add(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
return set.remove(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(@NotNull Collection<?> c) {
|
||||
return set.containsAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(@NotNull Collection<?> c) {
|
||||
if (set instanceof EnumSet<?>) {
|
||||
set.addAll(c);
|
||||
}
|
||||
|
||||
return super.addAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(@NotNull Collection<?> c) {
|
||||
return set.retainAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
set.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return set.toString();
|
||||
}
|
||||
|
||||
public ImposterEnumSet clone() {
|
||||
Set newSet;
|
||||
if (set instanceof EnumSet<?> enumSet) {
|
||||
newSet = enumSet.clone();
|
||||
} else {
|
||||
newSet = new HashSet();
|
||||
newSet.addAll(set);
|
||||
}
|
||||
|
||||
return new ImposterEnumSet(newSet, objectClass);
|
||||
}
|
||||
|
||||
private void typeCheck(Object object) {
|
||||
if (objectClass != DummyEnum.class) {
|
||||
if (!objectClass.isAssignableFrom(object.getClass())) {
|
||||
throw new ClassCastException(object.getClass() + " != " + objectClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.bukkit.craftbukkit.legacy.reroute;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.PARAMETER)
|
||||
public @interface InjectCompatibility {
|
||||
|
||||
String value();
|
||||
}
|
||||
@@ -6,6 +6,6 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||
public @interface NotInBukkit {
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||
public @interface RequireCompatibility {
|
||||
|
||||
String value();
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.bukkit.craftbukkit.legacy.reroute;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||
public @interface RequirePluginVersion {
|
||||
|
||||
String value() default "";
|
||||
|
||||
String minInclusive() default "";
|
||||
|
||||
String maxInclusive() default "";
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package org.bukkit.craftbukkit.legacy.reroute;
|
||||
|
||||
import org.bukkit.craftbukkit.util.ApiVersion;
|
||||
|
||||
public record RequirePluginVersionData(ApiVersion minInclusive, ApiVersion maxInclusive) {
|
||||
|
||||
public static RequirePluginVersionData create(RequirePluginVersion requirePluginVersion) {
|
||||
if (!requirePluginVersion.value().isBlank()) {
|
||||
if (!requirePluginVersion.minInclusive().isBlank() || !requirePluginVersion.maxInclusive().isBlank()) {
|
||||
throw new RuntimeException("When setting value, min inclusive and max inclusive data is not allowed.");
|
||||
}
|
||||
|
||||
return new RequirePluginVersionData(ApiVersion.getOrCreateVersion(requirePluginVersion.value()), ApiVersion.getOrCreateVersion(requirePluginVersion.value()));
|
||||
}
|
||||
|
||||
ApiVersion minInclusive = null;
|
||||
ApiVersion maxInclusive = null;
|
||||
|
||||
if (!requirePluginVersion.minInclusive().isBlank()) {
|
||||
minInclusive = ApiVersion.getOrCreateVersion(requirePluginVersion.minInclusive());
|
||||
}
|
||||
if (!requirePluginVersion.maxInclusive().isBlank()) {
|
||||
maxInclusive = ApiVersion.getOrCreateVersion(requirePluginVersion.maxInclusive());
|
||||
}
|
||||
|
||||
return new RequirePluginVersionData(minInclusive, maxInclusive);
|
||||
}
|
||||
|
||||
public boolean test(ApiVersion pluginVersion) {
|
||||
if (minInclusive != null && pluginVersion.isOlderThan(minInclusive)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (maxInclusive != null && pluginVersion.isNewerThan(maxInclusive)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
package org.bukkit.craftbukkit.legacy.reroute;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.Type;
|
||||
|
||||
public record RerouteArgument(Type type, boolean injectPluginName, boolean injectPluginVersion) {
|
||||
public record RerouteArgument(Type type, Type sourceType, boolean injectPluginName, boolean injectPluginVersion, @Nullable String injectCompatibility) {
|
||||
|
||||
/**
|
||||
* Converts the type string to the correct load opcode.
|
||||
@@ -30,8 +31,8 @@ public record RerouteArgument(Type type, boolean injectPluginName, boolean injec
|
||||
* @return the opcode of the type
|
||||
*/
|
||||
public int instruction() {
|
||||
if (injectPluginName() || injectPluginVersion()) {
|
||||
throw new IllegalStateException(String.format("Cannot get instruction for plugin name / version argument: %s", this));
|
||||
if (injectPluginName() || injectPluginVersion() || injectCompatibility() != null) {
|
||||
throw new IllegalStateException(String.format("Cannot get instruction for plugin name / version argument / compatibility: %s", this));
|
||||
}
|
||||
|
||||
return type.getOpcode(Opcodes.ILOAD);
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.bukkit.craftbukkit.legacy.reroute;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.PARAMETER)
|
||||
public @interface RerouteArgumentType {
|
||||
|
||||
String value();
|
||||
}
|
||||
@@ -54,13 +54,16 @@ public class RerouteBuilder {
|
||||
|
||||
for (Parameter parameter : method.getParameters()) {
|
||||
Type type = Type.getType(parameter.getType());
|
||||
int count = 0;
|
||||
boolean injectPluginName = false;
|
||||
boolean injectPluginVersion = false;
|
||||
String injectCompatibility = null;
|
||||
if (parameter.isAnnotationPresent(InjectPluginName.class)) {
|
||||
if (parameter.getType() != String.class) {
|
||||
throw new RuntimeException("Plugin name argument must be of type name, but got " + parameter.getType());
|
||||
}
|
||||
injectPluginName = true;
|
||||
count++;
|
||||
}
|
||||
|
||||
if (parameter.isAnnotationPresent(InjectPluginVersion.class)) {
|
||||
@@ -68,17 +71,39 @@ public class RerouteBuilder {
|
||||
throw new RuntimeException("Plugin version argument must be of type ApiVersion, but got " + parameter.getType());
|
||||
}
|
||||
injectPluginVersion = true;
|
||||
count++;
|
||||
}
|
||||
|
||||
if (injectPluginName && injectPluginVersion) {
|
||||
if (parameter.isAnnotationPresent(InjectCompatibility.class)) {
|
||||
if (parameter.getType() != boolean.class) {
|
||||
throw new RuntimeException("Compatibility argument must be of type boolean, but got " + parameter.getType());
|
||||
}
|
||||
injectCompatibility = parameter.getAnnotation(InjectCompatibility.class).value();
|
||||
count++;
|
||||
}
|
||||
|
||||
if (count > 1) {
|
||||
// This should not happen, since we check types,
|
||||
// and those two have different types -> it would already have failed
|
||||
throw new RuntimeException("Wtf?");
|
||||
}
|
||||
|
||||
RerouteArgument argument = new RerouteArgument(type, injectPluginName, injectPluginVersion);
|
||||
RerouteArgumentType rerouteArgumentType = parameter.getAnnotation(RerouteArgumentType.class);
|
||||
if (count == 1 && rerouteArgumentType != null) {
|
||||
// Why would you do this?
|
||||
throw new RuntimeException("Wtf?");
|
||||
}
|
||||
|
||||
Type sourceType;
|
||||
if (rerouteArgumentType != null) {
|
||||
sourceType = Type.getObjectType(rerouteArgumentType.value());
|
||||
} else {
|
||||
sourceType = type;
|
||||
}
|
||||
|
||||
RerouteArgument argument = new RerouteArgument(type, sourceType, injectPluginName, injectPluginVersion, injectCompatibility);
|
||||
arguments.add(argument);
|
||||
if (!injectPluginName && !injectPluginVersion) {
|
||||
if (count == 0) {
|
||||
sourceArguments.add(argument);
|
||||
}
|
||||
}
|
||||
@@ -89,10 +114,18 @@ public class RerouteBuilder {
|
||||
sourceOwner = Type.getObjectType(rerouteStatic.value());
|
||||
} else {
|
||||
RerouteArgument argument = sourceArguments.get(0);
|
||||
sourceOwner = argument.type();
|
||||
sourceOwner = argument.sourceType();
|
||||
sourceArguments.remove(argument);
|
||||
}
|
||||
Type sourceDesc = Type.getMethodType(rerouteReturn.type(), sourceArguments.stream().map(RerouteArgument::type).toArray(Type[]::new));
|
||||
|
||||
RerouteReturnType rerouteReturnType = method.getAnnotation(RerouteReturnType.class);
|
||||
Type returnType;
|
||||
if (rerouteReturnType != null) {
|
||||
returnType = Type.getObjectType(rerouteReturnType.value());
|
||||
} else {
|
||||
returnType = rerouteReturn.type();
|
||||
}
|
||||
Type sourceDesc = Type.getMethodType(returnType, sourceArguments.stream().map(RerouteArgument::sourceType).toArray(Type[]::new));
|
||||
|
||||
RerouteMethodName rerouteMethodName = method.getAnnotation(RerouteMethodName.class);
|
||||
String methodName;
|
||||
@@ -110,13 +143,22 @@ public class RerouteBuilder {
|
||||
|
||||
Type targetType = Type.getType(method);
|
||||
|
||||
boolean inBukkit = !method.isAnnotationPresent(NotInBukkit.class);
|
||||
boolean inBukkit = !method.isAnnotationPresent(NotInBukkit.class) && !method.getDeclaringClass().isAnnotationPresent(NotInBukkit.class);
|
||||
|
||||
String requiredCompatibility = null;
|
||||
if (method.isAnnotationPresent(RequireCompatibility.class)) {
|
||||
requiredCompatibility = method.getAnnotation(RequireCompatibility.class).value();
|
||||
} else if (method.getDeclaringClass().isAnnotationPresent(RequireCompatibility.class)) {
|
||||
requiredCompatibility = method.getDeclaringClass().getAnnotation(RequireCompatibility.class).value();
|
||||
}
|
||||
|
||||
return new RerouteMethodData(methodKey, sourceDesc, sourceOwner, methodName, rerouteStatic != null, targetType, Type.getInternalName(method.getDeclaringClass()), method.getName(), arguments, rerouteReturn, inBukkit, requiredCompatibility);
|
||||
RequirePluginVersionData requiredPluginVersion = null;
|
||||
if (method.isAnnotationPresent(RequirePluginVersion.class)) {
|
||||
requiredPluginVersion = RequirePluginVersionData.create(method.getAnnotation(RequirePluginVersion.class));
|
||||
} else if (method.getDeclaringClass().isAnnotationPresent(RequirePluginVersion.class)) {
|
||||
requiredPluginVersion = RequirePluginVersionData.create(method.getDeclaringClass().getAnnotation(RequirePluginVersion.class));
|
||||
}
|
||||
|
||||
return new RerouteMethodData(methodKey, sourceDesc, sourceOwner, methodName, rerouteStatic != null, targetType, Type.getInternalName(method.getDeclaringClass()), method.getName(), arguments, rerouteReturn, inBukkit, requiredCompatibility, requiredPluginVersion);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,5 +7,5 @@ import org.objectweb.asm.Type;
|
||||
public record RerouteMethodData(String source, Type sourceDesc, Type sourceOwner, String sourceName,
|
||||
boolean staticReroute, Type targetType, String targetOwner, String targetName,
|
||||
List<RerouteArgument> arguments, RerouteReturn rerouteReturn, boolean isInBukkit,
|
||||
@Nullable String requiredCompatibility) {
|
||||
@Nullable String requiredCompatibility, @Nullable RequirePluginVersionData requiredPluginVersion) {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.bukkit.craftbukkit.legacy.reroute;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface RerouteReturnType {
|
||||
|
||||
String value();
|
||||
}
|
||||
Reference in New Issue
Block a user