#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:
CraftBukkit/Spigot 2024-07-06 17:14:22 +10:00
parent f59f0d1c9b
commit 41b8d833db
27 changed files with 1424 additions and 117 deletions

View File

@ -9,7 +9,6 @@ import java.util.stream.Stream;
import net.minecraft.core.Holder;
import net.minecraft.core.IRegistry;
import net.minecraft.core.IRegistryCustom;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import org.bukkit.GameEvent;
@ -25,6 +24,9 @@ import org.bukkit.block.BlockType;
import org.bukkit.craftbukkit.block.CraftBlockType;
import org.bukkit.craftbukkit.damage.CraftDamageType;
import org.bukkit.craftbukkit.enchantments.CraftEnchantment;
import org.bukkit.craftbukkit.entity.CraftCat;
import org.bukkit.craftbukkit.entity.CraftFrog;
import org.bukkit.craftbukkit.entity.CraftVillager;
import org.bukkit.craftbukkit.entity.CraftWolf;
import org.bukkit.craftbukkit.generator.structure.CraftStructure;
import org.bukkit.craftbukkit.generator.structure.CraftStructureType;
@ -32,19 +34,24 @@ import org.bukkit.craftbukkit.inventory.CraftItemType;
import org.bukkit.craftbukkit.inventory.trim.CraftTrimMaterial;
import org.bukkit.craftbukkit.inventory.trim.CraftTrimPattern;
import org.bukkit.craftbukkit.legacy.FieldRename;
import org.bukkit.craftbukkit.map.CraftMapCursor;
import org.bukkit.craftbukkit.potion.CraftPotionEffectType;
import org.bukkit.craftbukkit.util.ApiVersion;
import org.bukkit.craftbukkit.util.CraftNamespacedKey;
import org.bukkit.craftbukkit.util.Handleable;
import org.bukkit.damage.DamageType;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Cat;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Frog;
import org.bukkit.entity.Villager;
import org.bukkit.entity.Wolf;
import org.bukkit.generator.structure.Structure;
import org.bukkit.generator.structure.StructureType;
import org.bukkit.inventory.ItemType;
import org.bukkit.inventory.meta.trim.TrimMaterial;
import org.bukkit.inventory.meta.trim.TrimPattern;
import org.bukkit.map.MapCursor;
import org.bukkit.potion.PotionEffectType;
import org.jetbrains.annotations.NotNull;
@ -136,7 +143,13 @@ public class CraftRegistry<B extends Keyed, M> implements Registry<B> {
return new CraftRegistry<>(Structure.class, registryHolder.registryOrThrow(Registries.STRUCTURE), CraftStructure::new, FieldRename.NONE);
}
if (bukkitClass == StructureType.class) {
return new CraftRegistry<>(StructureType.class, BuiltInRegistries.STRUCTURE_TYPE, CraftStructureType::new, FieldRename.NONE);
return new CraftRegistry<>(StructureType.class, registryHolder.registryOrThrow(Registries.STRUCTURE_TYPE), CraftStructureType::new, FieldRename.NONE);
}
if (bukkitClass == Villager.Type.class) {
return new CraftRegistry<>(Villager.Type.class, registryHolder.registryOrThrow(Registries.VILLAGER_TYPE), CraftVillager.CraftType::new, FieldRename.NONE);
}
if (bukkitClass == Villager.Profession.class) {
return new CraftRegistry<>(Villager.Profession.class, registryHolder.registryOrThrow(Registries.VILLAGER_PROFESSION), CraftVillager.CraftProfession::new, FieldRename.NONE);
}
if (bukkitClass == TrimMaterial.class) {
return new CraftRegistry<>(TrimMaterial.class, registryHolder.registryOrThrow(Registries.TRIM_MATERIAL), CraftTrimMaterial::new, FieldRename.NONE);
@ -159,6 +172,15 @@ public class CraftRegistry<B extends Keyed, M> implements Registry<B> {
if (bukkitClass == ItemType.class) {
return new CraftRegistry<>(ItemType.class, registryHolder.registryOrThrow(Registries.ITEM), CraftItemType::new, FieldRename.NONE);
}
if (bukkitClass == Frog.Variant.class) {
return new CraftRegistry<>(Frog.Variant.class, registryHolder.registryOrThrow(Registries.FROG_VARIANT), CraftFrog.CraftVariant::new, FieldRename.NONE);
}
if (bukkitClass == Cat.Type.class) {
return new CraftRegistry<>(Cat.Type.class, registryHolder.registryOrThrow(Registries.CAT_VARIANT), CraftCat.CraftType::new, FieldRename.NONE);
}
if (bukkitClass == MapCursor.Type.class) {
return new CraftRegistry<>(MapCursor.Type.class, registryHolder.registryOrThrow(Registries.MAP_DECORATION_TYPE), CraftMapCursor.CraftType::new, FieldRename.NONE);
}
return null;
}

View File

@ -443,6 +443,14 @@ public final class CraftServer implements Server {
logger.info("Using following compatibilities: `" + Joiner.on("`, `").join(activeCompatibilities) + "`, this will affect performance and other plugins behavior.");
logger.info("Only use when necessary and prefer updating plugins if possible.");
}
if (activeCompatibilities.contains("enum-compatibility-mode")) {
getLogger().warning("Loading plugins in enum compatibility mode. This will affect plugin performance. Use only as a transition period or when absolutely necessary.");
} else if (System.getProperty("RemoveEnumBanner") == null) {
// TODO 2024-06-16: Remove in newer version
getLogger().info("*** This version of Spigot contains changes to some enums. If you notice that plugins no longer work after updating, please report this to the developers of those plugins first. ***");
getLogger().info("*** If you cannot update those plugins, you can try setting `settings.compatibility.enum-compatibility-mode` to `true` in `bukkit.yml`. ***");
}
}
public void loadPlugins() {

View File

@ -1,17 +1,18 @@
package org.bukkit.craftbukkit.entity;
import com.google.common.base.Preconditions;
import java.util.Locale;
import net.minecraft.core.Holder;
import net.minecraft.core.IRegistry;
import net.minecraft.core.registries.Registries;
import net.minecraft.world.entity.animal.CatVariant;
import net.minecraft.world.entity.animal.EntityCat;
import net.minecraft.world.item.EnumColor;
import org.bukkit.DyeColor;
import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
import org.bukkit.craftbukkit.CraftRegistry;
import org.bukkit.craftbukkit.CraftServer;
import org.bukkit.craftbukkit.util.CraftNamespacedKey;
import org.bukkit.craftbukkit.util.Handleable;
import org.bukkit.entity.Cat;
public class CraftCat extends CraftTameableAnimal implements Cat {
@ -52,14 +53,11 @@ public class CraftCat extends CraftTameableAnimal implements Cat {
getHandle().setCollarColor(EnumColor.byId(color.getWoolData()));
}
public static class CraftType {
public static class CraftType implements Type, Handleable<CatVariant> {
private static int count = 0;
public static Type minecraftToBukkit(CatVariant minecraft) {
Preconditions.checkArgument(minecraft != null);
IRegistry<CatVariant> registry = CraftRegistry.getMinecraftRegistry(Registries.CAT_VARIANT);
return Registry.CAT_VARIANT.get(CraftNamespacedKey.fromMinecraft(registry.getKey(minecraft)));
return CraftRegistry.minecraftToBukkit(minecraft, Registries.CAT_VARIANT, Registry.CAT_VARIANT);
}
public static Type minecraftHolderToBukkit(Holder<CatVariant> minecraft) {
@ -67,24 +65,80 @@ public class CraftCat extends CraftTameableAnimal implements Cat {
}
public static CatVariant bukkitToMinecraft(Type bukkit) {
Preconditions.checkArgument(bukkit != null);
IRegistry<CatVariant> registry = CraftRegistry.getMinecraftRegistry(Registries.CAT_VARIANT);
return registry.get(CraftNamespacedKey.toMinecraft(bukkit.getKey()));
return CraftRegistry.bukkitToMinecraft(bukkit);
}
public static Holder<CatVariant> bukkitToMinecraftHolder(Type bukkit) {
Preconditions.checkArgument(bukkit != null);
return CraftRegistry.bukkitToMinecraftHolder(bukkit, Registries.CAT_VARIANT);
}
IRegistry<CatVariant> registry = CraftRegistry.getMinecraftRegistry(Registries.CAT_VARIANT);
private final NamespacedKey key;
private final CatVariant catVariant;
private final String name;
private final int ordinal;
if (registry.wrapAsHolder(bukkitToMinecraft(bukkit)) instanceof Holder.c<CatVariant> holder) {
return holder;
public CraftType(NamespacedKey key, CatVariant catVariant) {
this.key = key;
this.catVariant = catVariant;
// For backwards compatibility, minecraft values will still return the uppercase name without the namespace,
// in case plugins use for example the name as key in a config file to receive type specific values.
// Custom types will return the key with namespace. For a plugin this should look than like a new type
// (which can always be added in new minecraft versions and the plugin should therefore handle it accordingly).
if (NamespacedKey.MINECRAFT.equals(key.getNamespace())) {
this.name = key.getKey().toUpperCase(Locale.ROOT);
} else {
this.name = key.toString();
}
this.ordinal = count++;
}
@Override
public CatVariant getHandle() {
return catVariant;
}
@Override
public NamespacedKey getKey() {
return key;
}
@Override
public int compareTo(Type variant) {
return ordinal - variant.ordinal();
}
@Override
public String name() {
return name;
}
@Override
public int ordinal() {
return ordinal;
}
@Override
public String toString() {
// For backwards compatibility
return name();
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
throw new IllegalArgumentException("No Reference holder found for " + bukkit
+ ", this can happen if a plugin creates its own cat variant with out properly registering it.");
if (!(other instanceof CraftType)) {
return false;
}
return getKey().equals(((CraftType) other).getKey());
}
@Override
public int hashCode() {
return getKey().hashCode();
}
}
}

View File

@ -1,15 +1,16 @@
package org.bukkit.craftbukkit.entity;
import com.google.common.base.Preconditions;
import java.util.Locale;
import net.minecraft.core.Holder;
import net.minecraft.core.IRegistry;
import net.minecraft.core.registries.Registries;
import net.minecraft.world.entity.animal.FrogVariant;
import net.minecraft.world.entity.animal.frog.Frog;
import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
import org.bukkit.craftbukkit.CraftRegistry;
import org.bukkit.craftbukkit.CraftServer;
import org.bukkit.craftbukkit.util.CraftNamespacedKey;
import org.bukkit.craftbukkit.util.Handleable;
import org.bukkit.entity.Entity;
public class CraftFrog extends CraftAnimals implements org.bukkit.entity.Frog {
@ -54,17 +55,11 @@ public class CraftFrog extends CraftAnimals implements org.bukkit.entity.Frog {
getHandle().setVariant(CraftVariant.bukkitToMinecraftHolder(variant));
}
public static class CraftVariant {
public static class CraftVariant implements Variant, Handleable<FrogVariant> {
private static int count = 0;
public static Variant minecraftToBukkit(FrogVariant minecraft) {
Preconditions.checkArgument(minecraft != null);
IRegistry<FrogVariant> registry = CraftRegistry.getMinecraftRegistry(Registries.FROG_VARIANT);
Variant bukkit = Registry.FROG_VARIANT.get(CraftNamespacedKey.fromMinecraft(registry.getResourceKey(minecraft).orElseThrow().location()));
Preconditions.checkArgument(bukkit != null);
return bukkit;
return CraftRegistry.minecraftToBukkit(minecraft, Registries.FROG_VARIANT, Registry.FROG_VARIANT);
}
public static Variant minecraftHolderToBukkit(Holder<FrogVariant> minecraft) {
@ -72,23 +67,80 @@ public class CraftFrog extends CraftAnimals implements org.bukkit.entity.Frog {
}
public static FrogVariant bukkitToMinecraft(Variant bukkit) {
Preconditions.checkArgument(bukkit != null);
return CraftRegistry.getMinecraftRegistry(Registries.FROG_VARIANT)
.getOptional(CraftNamespacedKey.toMinecraft(bukkit.getKey())).orElseThrow();
return CraftRegistry.bukkitToMinecraft(bukkit);
}
public static Holder<FrogVariant> bukkitToMinecraftHolder(Variant bukkit) {
Preconditions.checkArgument(bukkit != null);
return CraftRegistry.bukkitToMinecraftHolder(bukkit, Registries.FROG_VARIANT);
}
IRegistry<FrogVariant> registry = CraftRegistry.getMinecraftRegistry(Registries.FROG_VARIANT);
private final NamespacedKey key;
private final FrogVariant frogVariant;
private final String name;
private final int ordinal;
if (registry.wrapAsHolder(bukkitToMinecraft(bukkit)) instanceof Holder.c<FrogVariant> holder) {
return holder;
public CraftVariant(NamespacedKey key, FrogVariant frogVariant) {
this.key = key;
this.frogVariant = frogVariant;
// For backwards compatibility, minecraft values will still return the uppercase name without the namespace,
// in case plugins use for example the name as key in a config file to receive variant specific values.
// Custom variants will return the key with namespace. For a plugin this should look than like a new variant
// (which can always be added in new minecraft versions and the plugin should therefore handle it accordingly).
if (NamespacedKey.MINECRAFT.equals(key.getNamespace())) {
this.name = key.getKey().toUpperCase(Locale.ROOT);
} else {
this.name = key.toString();
}
this.ordinal = count++;
}
@Override
public FrogVariant getHandle() {
return frogVariant;
}
@Override
public NamespacedKey getKey() {
return key;
}
@Override
public int compareTo(Variant variant) {
return ordinal - variant.ordinal();
}
@Override
public String name() {
return name;
}
@Override
public int ordinal() {
return ordinal;
}
@Override
public String toString() {
// For backwards compatibility
return name();
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
throw new IllegalArgumentException("No Reference holder found for " + bukkit
+ ", this can happen if a plugin creates its own frog variant with out properly registering it.");
if (!(other instanceof CraftVariant)) {
return false;
}
return getKey().equals(((Variant) other).getKey());
}
@Override
public int hashCode() {
return getKey().hashCode();
}
}
}

View File

@ -1,8 +1,8 @@
package org.bukkit.craftbukkit.entity;
import com.google.common.base.Preconditions;
import java.util.Locale;
import net.minecraft.core.BlockPosition;
import net.minecraft.core.IRegistry;
import net.minecraft.core.registries.Registries;
import net.minecraft.world.entity.monster.EntityZombie;
import net.minecraft.world.entity.monster.EntityZombieVillager;
@ -12,11 +12,12 @@ import net.minecraft.world.entity.npc.VillagerType;
import net.minecraft.world.level.block.BlockBed;
import net.minecraft.world.level.block.state.IBlockData;
import org.bukkit.Location;
import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
import org.bukkit.craftbukkit.CraftRegistry;
import org.bukkit.craftbukkit.CraftServer;
import org.bukkit.craftbukkit.util.CraftLocation;
import org.bukkit.craftbukkit.util.CraftNamespacedKey;
import org.bukkit.craftbukkit.util.Handleable;
import org.bukkit.entity.Villager;
import org.bukkit.entity.ZombieVillager;
import org.bukkit.event.entity.CreatureSpawnEvent;
@ -126,45 +127,165 @@ public class CraftVillager extends CraftAbstractVillager implements Villager {
return (entityzombievillager != null) ? (ZombieVillager) entityzombievillager.getBukkitEntity() : null;
}
public static class CraftType {
public static class CraftType implements Type, Handleable<VillagerType> {
private static int count = 0;
public static Type minecraftToBukkit(VillagerType minecraft) {
Preconditions.checkArgument(minecraft != null);
IRegistry<VillagerType> registry = CraftRegistry.getMinecraftRegistry(Registries.VILLAGER_TYPE);
Type bukkit = Registry.VILLAGER_TYPE.get(CraftNamespacedKey.fromMinecraft(registry.getResourceKey(minecraft).orElseThrow().location()));
Preconditions.checkArgument(bukkit != null);
return bukkit;
return CraftRegistry.minecraftToBukkit(minecraft, Registries.VILLAGER_TYPE, Registry.VILLAGER_TYPE);
}
public static VillagerType bukkitToMinecraft(Type bukkit) {
Preconditions.checkArgument(bukkit != null);
return CraftRegistry.bukkitToMinecraft(bukkit);
}
return CraftRegistry.getMinecraftRegistry(Registries.VILLAGER_TYPE)
.getOptional(CraftNamespacedKey.toMinecraft(bukkit.getKey())).orElseThrow();
private final NamespacedKey key;
private final VillagerType villagerType;
private final String name;
private final int ordinal;
public CraftType(NamespacedKey key, VillagerType villagerType) {
this.key = key;
this.villagerType = villagerType;
// For backwards compatibility, minecraft values will still return the uppercase name without the namespace,
// in case plugins use for example the name as key in a config file to receive type specific values.
// Custom types will return the key with namespace. For a plugin this should look than like a new type
// (which can always be added in new minecraft versions and the plugin should therefore handle it accordingly).
if (NamespacedKey.MINECRAFT.equals(key.getNamespace())) {
this.name = key.getKey().toUpperCase(Locale.ROOT);
} else {
this.name = key.toString();
}
this.ordinal = count++;
}
@Override
public VillagerType getHandle() {
return villagerType;
}
@Override
public NamespacedKey getKey() {
return key;
}
@Override
public int compareTo(Type type) {
return ordinal - type.ordinal();
}
@Override
public String name() {
return name;
}
@Override
public int ordinal() {
return ordinal;
}
@Override
public String toString() {
// For backwards compatibility
return name();
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof CraftType)) {
return false;
}
return getKey().equals(((Type) other).getKey());
}
@Override
public int hashCode() {
return getKey().hashCode();
}
}
public static class CraftProfession {
public static class CraftProfession implements Profession, Handleable<VillagerProfession> {
private static int count = 0;
public static Profession minecraftToBukkit(VillagerProfession minecraft) {
Preconditions.checkArgument(minecraft != null);
IRegistry<VillagerProfession> registry = CraftRegistry.getMinecraftRegistry(Registries.VILLAGER_PROFESSION);
Profession bukkit = Registry.VILLAGER_PROFESSION.get(CraftNamespacedKey.fromMinecraft(registry.getResourceKey(minecraft).orElseThrow().location()));
Preconditions.checkArgument(bukkit != null);
return bukkit;
return CraftRegistry.minecraftToBukkit(minecraft, Registries.VILLAGER_PROFESSION, Registry.VILLAGER_PROFESSION);
}
public static VillagerProfession bukkitToMinecraft(Profession bukkit) {
Preconditions.checkArgument(bukkit != null);
return CraftRegistry.bukkitToMinecraft(bukkit);
}
return CraftRegistry.getMinecraftRegistry(Registries.VILLAGER_PROFESSION)
.getOptional(CraftNamespacedKey.toMinecraft(bukkit.getKey())).orElseThrow();
private final NamespacedKey key;
private final VillagerProfession villagerProfession;
private final String name;
private final int ordinal;
public CraftProfession(NamespacedKey key, VillagerProfession villagerProfession) {
this.key = key;
this.villagerProfession = villagerProfession;
// For backwards compatibility, minecraft values will still return the uppercase name without the namespace,
// in case plugins use for example the name as key in a config file to receive profession specific values.
// Custom professions will return the key with namespace. For a plugin this should look than like a new profession
// (which can always be added in new minecraft versions and the plugin should therefore handle it accordingly).
if (NamespacedKey.MINECRAFT.equals(key.getNamespace())) {
this.name = key.getKey().toUpperCase(Locale.ROOT);
} else {
this.name = key.toString();
}
this.ordinal = count++;
}
@Override
public VillagerProfession getHandle() {
return villagerProfession;
}
@Override
public NamespacedKey getKey() {
return key;
}
@Override
public int compareTo(Profession profession) {
return ordinal - profession.ordinal();
}
@Override
public String name() {
return name;
}
@Override
public int ordinal() {
return ordinal;
}
@Override
public String toString() {
// For backwards compatibility
return name();
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof CraftProfession)) {
return false;
}
return getKey().equals(((Profession) other).getKey());
}
@Override
public int hashCode() {
return getKey().hashCode();
}
}
}

View File

@ -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

View File

@ -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 {
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}
}

View File

@ -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);
}
}
}
}

View File

@ -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();
}

View File

@ -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 {
}

View File

@ -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();

View File

@ -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 "";
}

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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();
}

View File

@ -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);
}
}

View File

@ -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) {
}

View File

@ -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();
}

View File

@ -1,28 +1,23 @@
package org.bukkit.craftbukkit.map;
import com.google.common.base.Preconditions;
import java.util.Locale;
import net.minecraft.core.Holder;
import net.minecraft.core.IRegistry;
import net.minecraft.core.registries.Registries;
import net.minecraft.world.level.saveddata.maps.MapDecorationType;
import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
import org.bukkit.craftbukkit.CraftRegistry;
import org.bukkit.craftbukkit.util.CraftNamespacedKey;
import org.bukkit.craftbukkit.util.Handleable;
import org.bukkit.map.MapCursor;
public final class CraftMapCursor {
public static final class CraftType {
public static final class CraftType implements MapCursor.Type, Handleable<MapDecorationType> {
private static int count = 0;
public static MapCursor.Type minecraftToBukkit(MapDecorationType minecraft) {
Preconditions.checkArgument(minecraft != null);
IRegistry<MapDecorationType> registry = CraftRegistry.getMinecraftRegistry(Registries.MAP_DECORATION_TYPE);
MapCursor.Type bukkit = Registry.MAP_DECORATION_TYPE.get(CraftNamespacedKey.fromMinecraft(registry.getResourceKey(minecraft).orElseThrow().location()));
Preconditions.checkArgument(bukkit != null);
return bukkit;
return CraftRegistry.minecraftToBukkit(minecraft, Registries.MAP_DECORATION_TYPE, Registry.MAP_DECORATION_TYPE);
}
public static MapCursor.Type minecraftHolderToBukkit(Holder<MapDecorationType> minecraft) {
@ -30,23 +25,85 @@ public final class CraftMapCursor {
}
public static MapDecorationType bukkitToMinecraft(MapCursor.Type bukkit) {
Preconditions.checkArgument(bukkit != null);
return CraftRegistry.getMinecraftRegistry(Registries.MAP_DECORATION_TYPE)
.getOptional(CraftNamespacedKey.toMinecraft(bukkit.getKey())).orElseThrow();
return CraftRegistry.bukkitToMinecraft(bukkit);
}
public static Holder<MapDecorationType> bukkitToMinecraftHolder(MapCursor.Type bukkit) {
Preconditions.checkArgument(bukkit != null);
return CraftRegistry.bukkitToMinecraftHolder(bukkit, Registries.MAP_DECORATION_TYPE);
}
IRegistry<MapDecorationType> registry = CraftRegistry.getMinecraftRegistry(Registries.MAP_DECORATION_TYPE);
private final NamespacedKey key;
private final MapDecorationType mapDecorationType;
private final String name;
private final int ordinal;
if (registry.wrapAsHolder(bukkitToMinecraft(bukkit)) instanceof Holder.c<MapDecorationType> holder) {
return holder;
public CraftType(NamespacedKey key, MapDecorationType mapDecorationType) {
this.key = key;
this.mapDecorationType = mapDecorationType;
// For backwards compatibility, minecraft values will still return the uppercase name without the namespace,
// in case plugins use for example the name as key in a config file to receive type specific values.
// Custom types will return the key with namespace. For a plugin this should look than like a new type
// (which can always be added in new minecraft versions and the plugin should therefore handle it accordingly).
if (NamespacedKey.MINECRAFT.equals(key.getNamespace())) {
this.name = key.getKey().toUpperCase(Locale.ROOT);
} else {
this.name = key.toString();
}
this.ordinal = count++;
}
@Override
public MapDecorationType getHandle() {
return mapDecorationType;
}
@Override
public NamespacedKey getKey() {
return key;
}
@Override
public int compareTo(MapCursor.Type type) {
return ordinal - type.ordinal();
}
@Override
public String name() {
return name;
}
@Override
public int ordinal() {
return ordinal;
}
@Override
public String toString() {
// For backwards compatibility
return name();
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
throw new IllegalArgumentException("No Reference holder found for " + bukkit
+ ", this can happen if a plugin creates its own map cursor type without properly registering it.");
if (!(other instanceof CraftType)) {
return false;
}
return getKey().equals(((MapCursor.Type) other).getKey());
}
@Override
public int hashCode() {
return getKey().hashCode();
}
@Override
public byte getValue() {
return (byte) CraftRegistry.getMinecraftRegistry(Registries.MAP_DECORATION_TYPE).getId(getHandle());
}
}
}

View File

@ -1,10 +1,11 @@
package org.bukkit.craftbukkit.util;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
public final class ApiVersion implements Comparable<ApiVersion> {
public final class ApiVersion implements Comparable<ApiVersion>, Serializable {
public static final ApiVersion CURRENT;
public static final ApiVersion FLATTENING;
@ -122,4 +123,6 @@ public final class ApiVersion implements Comparable<ApiVersion> {
public String toString() {
return getVersionString();
}
private static final long serialVersionUID = 0L;
}

View File

@ -24,6 +24,7 @@ import org.bukkit.Material;
import org.bukkit.craftbukkit.legacy.FieldRename;
import org.bukkit.craftbukkit.legacy.MaterialRerouting;
import org.bukkit.craftbukkit.legacy.MethodRerouting;
import org.bukkit.craftbukkit.legacy.enums.EnumEvil;
import org.bukkit.craftbukkit.legacy.reroute.RerouteArgument;
import org.bukkit.craftbukkit.legacy.reroute.RerouteBuilder;
import org.bukkit.craftbukkit.legacy.reroute.RerouteMethodData;
@ -61,6 +62,12 @@ public class Commodore {
"org/bukkit/inventory/ItemStack (S)V setDurability"
));
private static final Map<String, String> ENUM_RENAMES = Map.of(
"java/lang/Enum", "java/lang/Object",
"java/util/EnumSet", "org/bukkit/craftbukkit/legacy/enums/ImposterEnumSet",
"java/util/EnumMap", "org/bukkit/craftbukkit/legacy/enums/ImposterEnumMap"
);
private static final Map<String, String> RENAMES = Map.of(
"org/bukkit/entity/TextDisplay$TextAligment", "org/bukkit/entity/TextDisplay$TextAlignment", // SPIGOT-7335
"org/spigotmc/event/entity/EntityMountEvent", "org/bukkit/event/entity/EntityMountEvent",
@ -68,7 +75,12 @@ public class Commodore {
);
private static final Map<String, String> CLASS_TO_INTERFACE = Map.of(
"org/bukkit/inventory/InventoryView", "org/bukkit/craftbukkit/inventory/CraftAbstractInventoryView"
"org/bukkit/inventory/InventoryView", "org/bukkit/craftbukkit/inventory/CraftAbstractInventoryView",
"org/bukkit/entity/Villager$Type", "NOP",
"org/bukkit/entity/Villager$Profession", "NOP",
"org/bukkit/entity/Frog$Variant", "NOP",
"org/bukkit/entity/Cat$Type", "NOP",
"org/bukkit/map/MapCursor$Type", "NOP"
);
private static Map<String, RerouteMethodData> createReroutes(Class<?> clazz) {
@ -82,6 +94,7 @@ public class Commodore {
private static final Map<String, RerouteMethodData> FIELD_RENAME_METHOD_REROUTE = createReroutes(FieldRename.class);
private static final Map<String, RerouteMethodData> MATERIAL_METHOD_REROUTE = createReroutes(MaterialRerouting.class);
private static final Map<String, RerouteMethodData> METHOD_REROUTE = createReroutes(MethodRerouting.class);
private static final Map<String, RerouteMethodData> ENUM_METHOD_REROUTE = createReroutes(EnumEvil.class);
public static void main(String[] args) {
OptionParser parser = new OptionParser();
@ -148,9 +161,14 @@ public class Commodore {
public static byte[] convert(byte[] b, final String pluginName, final ApiVersion pluginVersion, final Set<String> activeCompatibilities) {
final boolean modern = pluginVersion.isNewerThanOrSameAs(ApiVersion.FLATTENING);
ClassReader cr = new ClassReader(b);
ClassWriter cw = new ClassWriter(cr, 0);
ClassWriter cw = new ClassWriter(0); // TODO 2024-06-22: Open PR to ASM to included interface in handle hash
cr.accept(new ClassRemapper(new ClassVisitor(Opcodes.ASM9, cw) {
ClassVisitor visitor = cw;
if (pluginVersion.isOlderThanOrSameAs(ApiVersion.getOrCreateVersion("1.20.6")) && activeCompatibilities.contains("enum-compatibility-mode")) {
visitor = new LimitedClassRemapper(cw, new SimpleRemapper(ENUM_RENAMES));
}
cr.accept(new ClassRemapper(new ClassVisitor(Opcodes.ASM9, visitor) {
final Set<RerouteMethodData> rerouteMethodData = new HashSet<>();
String className;
boolean isInterface;
@ -179,8 +197,10 @@ public class Commodore {
} else if (argument.injectPluginVersion()) {
methodVisitor.visitLdcInsn(pluginVersion.getVersionString());
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(ApiVersion.class), "getOrCreateVersion", "(Ljava/lang/String;)L" + Type.getInternalName(ApiVersion.class) + ";", false);
} else if (argument.injectCompatibility() != null) {
methodVisitor.visitLdcInsn(activeCompatibilities.contains(argument.injectCompatibility()));
} else {
methodVisitor.visitIntInsn(argument.instruction(), index);
methodVisitor.visitVarInsn(argument.instruction(), index);
index++;
// Long and double need two space
@ -313,6 +333,10 @@ public class Commodore {
}
}
if (checkReroute(visitor, ENUM_METHOD_REROUTE, opcode, owner, name, desc, samMethodType, instantiatedMethodType)) {
return;
}
// SPIGOT-4496
if (owner.equals("org/bukkit/map/MapView") && name.equals("getId") && desc.equals("()S")) {
// Should be same size on stack so just call other method
@ -412,7 +436,7 @@ public class Commodore {
}
private boolean checkReroute(MethodPrinter visitor, Map<String, RerouteMethodData> rerouteMethodDataMap, int opcode, String owner, String name, String desc, Type samMethodType, Type instantiatedMethodType) {
return rerouteMethods(activeCompatibilities, rerouteMethodDataMap, opcode == Opcodes.INVOKESTATIC || opcode == Opcodes.H_INVOKESTATIC, owner, name, desc, data -> {
return rerouteMethods(activeCompatibilities, pluginVersion, rerouteMethodDataMap, opcode == Opcodes.INVOKESTATIC || opcode == Opcodes.H_INVOKESTATIC, owner, name, desc, data -> {
visitor.visit(Opcodes.INVOKESTATIC, className, buildMethodName(data), buildMethodDesc(data), isInterface, samMethodType, instantiatedMethodType);
rerouteMethodData.add(data);
});
@ -582,7 +606,7 @@ public class Commodore {
But since it is only applied for each class and method call once when they get first loaded, it should not be that bad.
(Although some load time testing could be done)
*/
public static boolean rerouteMethods(Set<String> activeCompatibilities, Map<String, RerouteMethodData> rerouteMethodDataMap, boolean staticCall, String owner, String name, String desc, Consumer<RerouteMethodData> consumer) {
public static boolean rerouteMethods(Set<String> activeCompatibilities, ApiVersion pluginVersion, Map<String, RerouteMethodData> rerouteMethodDataMap, boolean staticCall, String owner, String name, String desc, Consumer<RerouteMethodData> consumer) {
Type ownerType = Type.getObjectType(owner);
Class<?> ownerClass;
try {
@ -609,6 +633,10 @@ public class Commodore {
return false;
}
if (data.requiredPluginVersion() != null && !data.requiredPluginVersion().test(pluginVersion)) {
return false;
}
consumer.accept(data);
return true;
}
@ -621,7 +649,7 @@ public class Commodore {
}
private static String buildMethodDesc(RerouteMethodData rerouteMethodData) {
return Type.getMethodDescriptor(rerouteMethodData.sourceDesc().getReturnType(), rerouteMethodData.arguments().stream().filter(a -> !a.injectPluginName()).filter(a -> !a.injectPluginVersion()).map(RerouteArgument::type).toArray(Type[]::new));
return Type.getMethodDescriptor(rerouteMethodData.sourceDesc().getReturnType(), rerouteMethodData.arguments().stream().filter(a -> !a.injectPluginName()).filter(a -> !a.injectPluginVersion()).filter(a -> a.injectCompatibility() == null).map(RerouteArgument::sourceType).toArray(Type[]::new));
}
@FunctionalInterface

View File

@ -0,0 +1,43 @@
package org.bukkit.craftbukkit.util;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.commons.ClassRemapper;
import org.objectweb.asm.commons.MethodRemapper;
import org.objectweb.asm.commons.Remapper;
public class LimitedClassRemapper extends ClassRemapper {
public LimitedClassRemapper(ClassVisitor classVisitor, Remapper remapper) {
super(classVisitor, remapper);
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
this.className = name;
// We do not want to remap superName and interfaces for the enums
cv.visit(version, access, this.remapper.mapType(name), this.remapper.mapSignature(signature, false), superName, interfaces);
}
@Override
protected MethodVisitor createMethodRemapper(MethodVisitor methodVisitor) {
return new LimitedMethodRemapper(api, methodVisitor, remapper);
}
private class LimitedMethodRemapper extends MethodRemapper {
protected LimitedMethodRemapper(int api, MethodVisitor methodVisitor, Remapper remapper) {
super(api, methodVisitor, remapper);
}
@Override
public void visitMethodInsn(int opcodeAndSource, String owner, String name, String descriptor, boolean isInterface) {
if (owner != null && owner.equals("java/lang/Enum") && name != null && name.equals("<init>")) {
// We also do not want to remap the init method for enums
mv.visitMethodInsn(opcodeAndSource, owner, name, descriptor, isInterface);
return;
}
super.visitMethodInsn(opcodeAndSource, owner, name, descriptor, isInterface);
}
}
}

View File

@ -25,6 +25,7 @@ settings:
use-map-color-cache: true
compatibility:
allow-old-keys-in-registry: false
enum-compatibility-mode: false
spawn-limits:
monsters: 70
animals: 10

View File

@ -16,6 +16,7 @@ import java.util.stream.Stream;
import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.legacy.reroute.RerouteBuilder;
import org.bukkit.craftbukkit.legacy.reroute.RerouteMethodData;
import org.bukkit.craftbukkit.util.ApiVersion;
import org.bukkit.craftbukkit.util.Commodore;
import org.bukkit.support.AbstractTestingBase;
import org.junit.jupiter.api.AfterAll;
@ -93,7 +94,7 @@ public class MaterialReroutingTest extends AbstractTestingBase {
}
}
if (!Commodore.rerouteMethods(Collections.emptySet(), MATERIAL_METHOD_REROUTE, (methodNode.access & Opcodes.ACC_STATIC) != 0, classNode.name, methodNode.name, methodNode.desc, a -> { })) {
if (!Commodore.rerouteMethods(Collections.emptySet(), ApiVersion.CURRENT, MATERIAL_METHOD_REROUTE, (methodNode.access & Opcodes.ACC_STATIC) != 0, classNode.name, methodNode.name, methodNode.desc, a -> { })) {
missingReroute.add(methodNode.name + " " + methodNode.desc + " " + methodNode.signature);
}
}

View File

@ -6,8 +6,13 @@ import java.util.stream.Stream;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.effect.MobEffectList;
import net.minecraft.world.entity.animal.CatVariant;
import net.minecraft.world.entity.animal.FrogVariant;
import net.minecraft.world.entity.animal.WolfVariant;
import net.minecraft.world.entity.npc.VillagerProfession;
import net.minecraft.world.entity.npc.VillagerType;
import net.minecraft.world.item.Instrument;
import net.minecraft.world.level.saveddata.maps.MapDecorationType;
import org.bukkit.GameEvent;
import org.bukkit.JukeboxSong;
import org.bukkit.MusicInstrument;
@ -18,21 +23,29 @@ import org.bukkit.craftbukkit.CraftMusicInstrument;
import org.bukkit.craftbukkit.block.CraftBlockType;
import org.bukkit.craftbukkit.damage.CraftDamageType;
import org.bukkit.craftbukkit.enchantments.CraftEnchantment;
import org.bukkit.craftbukkit.entity.CraftCat;
import org.bukkit.craftbukkit.entity.CraftFrog;
import org.bukkit.craftbukkit.entity.CraftVillager;
import org.bukkit.craftbukkit.entity.CraftWolf;
import org.bukkit.craftbukkit.generator.structure.CraftStructure;
import org.bukkit.craftbukkit.generator.structure.CraftStructureType;
import org.bukkit.craftbukkit.inventory.CraftItemType;
import org.bukkit.craftbukkit.inventory.trim.CraftTrimMaterial;
import org.bukkit.craftbukkit.inventory.trim.CraftTrimPattern;
import org.bukkit.craftbukkit.map.CraftMapCursor;
import org.bukkit.craftbukkit.potion.CraftPotionEffectType;
import org.bukkit.damage.DamageType;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Cat;
import org.bukkit.entity.Frog;
import org.bukkit.entity.Villager;
import org.bukkit.entity.Wolf;
import org.bukkit.generator.structure.Structure;
import org.bukkit.generator.structure.StructureType;
import org.bukkit.inventory.ItemType;
import org.bukkit.inventory.meta.trim.TrimMaterial;
import org.bukkit.inventory.meta.trim.TrimPattern;
import org.bukkit.map.MapCursor;
import org.bukkit.potion.PotionEffectType;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.params.provider.Arguments;
@ -50,6 +63,8 @@ public class RegistriesArgumentProvider implements ArgumentsProvider {
register(PotionEffectType.class, Registries.MOB_EFFECT, CraftPotionEffectType.class, MobEffectList.class);
register(Structure.class, Registries.STRUCTURE, CraftStructure.class, net.minecraft.world.level.levelgen.structure.Structure.class);
register(StructureType.class, Registries.STRUCTURE_TYPE, CraftStructureType.class, net.minecraft.world.level.levelgen.structure.StructureType.class);
register(Villager.Type.class, Registries.VILLAGER_TYPE, CraftVillager.CraftType.class, VillagerType.class);
register(Villager.Profession.class, Registries.VILLAGER_PROFESSION, CraftVillager.CraftProfession.class, VillagerProfession.class);
register(TrimMaterial.class, Registries.TRIM_MATERIAL, CraftTrimMaterial.class, net.minecraft.world.item.armortrim.TrimMaterial.class);
register(TrimPattern.class, Registries.TRIM_PATTERN, CraftTrimPattern.class, net.minecraft.world.item.armortrim.TrimPattern.class);
register(DamageType.class, Registries.DAMAGE_TYPE, CraftDamageType.class, net.minecraft.world.damagesource.DamageType.class);
@ -57,6 +72,9 @@ public class RegistriesArgumentProvider implements ArgumentsProvider {
register(Wolf.Variant.class, Registries.WOLF_VARIANT, CraftWolf.CraftVariant.class, WolfVariant.class);
register(ItemType.class, Registries.ITEM, CraftItemType.class, net.minecraft.world.item.Item.class, true);
register(BlockType.class, Registries.BLOCK, CraftBlockType.class, net.minecraft.world.level.block.Block.class, true);
register(Frog.Variant.class, Registries.FROG_VARIANT, CraftFrog.CraftVariant.class, FrogVariant.class);
register(Cat.Type.class, Registries.CAT_VARIANT, CraftCat.CraftType.class, CatVariant.class);
register(MapCursor.Type.class, Registries.MAP_DECORATION_TYPE, CraftMapCursor.CraftType.class, MapDecorationType.class);
}