Co-authored-by: Bjarne Koll <git@lynxplay.dev>
Co-authored-by: Jake Potrebic <jake.m.potrebic@gmail.com>
Co-authored-by: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com>
Co-authored-by: MiniDigger | Martin <admin@minidigger.dev>
Co-authored-by: Nassim Jahnke <nassim@njahnke.dev>
Co-authored-by: Noah van der Aa <ndvdaa@gmail.com>
Co-authored-by: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
Co-authored-by: Shane Freeder <theboyetronic@gmail.com>
Co-authored-by: Spottedleaf <Spottedleaf@users.noreply.github.com>
Co-authored-by: Tamion <70228790+notTamion@users.noreply.github.com>
Co-authored-by: Warrior <50800980+Warriorrrr@users.noreply.github.com>
This commit is contained in:
Nassim Jahnke
2025-04-12 17:26:44 +02:00
parent 0767902699
commit f00727c57e
2092 changed files with 50551 additions and 48729 deletions

View File

@ -0,0 +1,3 @@
[*.java]
ij_java_generate_final_locals = false
ij_java_generate_final_parameters = false

View File

@ -0,0 +1,178 @@
import io.papermc.paperweight.util.defaultJavaLauncher
plugins {
java
id("io.papermc.paperweight.source-generator")
}
paperweight {
atFile.set(layout.projectDirectory.file("wideners.at"))
}
val serverRuntimeClasspath by configurations.registering { // resolvable?
isCanBeConsumed = false
isCanBeResolved = true
}
dependencies {
minecraftJar(project(":paper-server", "mappedJarOutgoing"))
implementation(project(":paper-server", "macheMinecraftLibraries"))
implementation("com.squareup:javapoet:1.13.0")
implementation(project(":paper-api"))
implementation("io.papermc.typewriter:typewriter:1.0.1") {
isTransitive = false // paper-api already have everything
}
implementation("info.picocli:picocli:4.7.6")
implementation("io.github.classgraph:classgraph:4.8.47")
implementation("org.jetbrains:annotations:26.0.1")
testImplementation("org.junit.jupiter:junit-jupiter:5.10.2")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
serverRuntimeClasspath(project(":paper-server", "runtimeConfiguration"))
}
val gameVersion = providers.gradleProperty("mcVersion")
val rewriteApi = tasks.registerGenerationTask("rewriteApi", true, "api", {
bootstrapTags = true
sourceSet = rootProject.layout.projectDirectory.dir("paper-api")
}) {
description = "Rewrite existing API classes"
classpath(sourceSets.main.map { it.runtimeClasspath })
}
val rewriteImpl = tasks.registerGenerationTask("rewriteImpl", true, "impl", {
sourceSet = rootProject.layout.projectDirectory.dir("paper-server")
serverClassPath.from(serverRuntimeClasspath)
}) {
description = "Rewrite existing implementation classes"
classpath(sourceSets.main.map { it.runtimeClasspath })
}
tasks.register("rewrite") {
group = "generation"
description = "Rewrite existing API classes and its implementation"
dependsOn(rewriteApi, rewriteImpl)
}
val generateApi = tasks.registerGenerationTask("generateApi", false, "api", {
bootstrapTags = true
sourceSet = rootProject.layout.projectDirectory.dir("paper-api")
}) {
description = "Generate new API classes"
classpath(sourceSets.main.map { it.runtimeClasspath })
}
val generateImpl = tasks.registerGenerationTask("generateImpl", false, "impl", {
sourceSet = rootProject.layout.projectDirectory.dir("paper-server")
}) {
description = "Generate new implementation classes"
classpath(sourceSets.main.map { it.runtimeClasspath })
}
tasks.register("generate") {
group = "generation"
description = "Generate new API classes and its implementation"
dependsOn(generateApi, generateImpl)
}
if (providers.gradleProperty("updatingMinecraft").getOrElse("false").toBoolean()) {
val scanOldGeneratedSourceCode by tasks.registering(JavaExec::class) {
group = "verification"
description = "Scan source code to detect outdated generated code"
javaLauncher = javaToolchains.defaultJavaLauncher(project)
mainClass.set("io.papermc.generator.rewriter.utils.ScanOldGeneratedSourceCode")
classpath(sourceSets.main.map { it.runtimeClasspath })
val projectDirs = listOf("paper-api", "paper-server").map { rootProject.layout.projectDirectory.dir(it) }
args(projectDirs.map { it.asFile.absolutePath })
val workDirs = projectDirs.map { it.dir("src/main/java") }
workDirs.forEach { inputs.dir(it) }
inputs.property("gameVersion", gameVersion)
outputs.dirs(workDirs)
}
tasks.check {
dependsOn(scanOldGeneratedSourceCode)
}
}
fun TaskContainer.registerGenerationTask(
name: String,
rewrite: Boolean,
side: String,
args: (GenerationArgumentProvider.() -> Unit)? = null,
block: JavaExec.() -> Unit
): TaskProvider<JavaExec> = register<JavaExec>(name) {
group = "generation"
dependsOn(project.tasks.test)
javaLauncher = project.javaToolchains.defaultJavaLauncher(project)
inputs.property("gameVersion", gameVersion)
inputs.dir(layout.projectDirectory.dir("src/main/java")).withPathSensitivity(PathSensitivity.RELATIVE)
mainClass.set("io.papermc.generator.Main")
systemProperty("paper.updatingMinecraft", providers.gradleProperty("updatingMinecraft").getOrElse("false").toBoolean())
val provider = objects.newInstance<GenerationArgumentProvider>()
provider.side = side
provider.rewrite = rewrite
if (args != null) {
args(provider)
}
argumentProviders.add(provider)
val targetDir = if (rewrite) "src/main/java" else "src/generated/java"
outputs.dir(provider.sourceSet.dir(targetDir))
block(this)
}
@Suppress("LeakingThis")
abstract class GenerationArgumentProvider : CommandLineArgumentProvider {
@get:PathSensitive(PathSensitivity.NONE)
@get:InputDirectory
abstract val sourceSet: DirectoryProperty
@get:Input
abstract val rewrite: Property<Boolean>
@get:Input
abstract val side: Property<String>
@get:CompileClasspath
abstract val serverClassPath: ConfigurableFileCollection
@get:Input
@get:Optional
abstract val bootstrapTags: Property<Boolean>
init {
bootstrapTags.convention(false)
}
override fun asArguments(): Iterable<String> {
val args = mutableListOf<String>()
args.add("--sourceset=${sourceSet.get().asFile.absolutePath}")
args.add("--side=${side.get()}")
args.add("--classpath=${serverClassPath.asPath}")
if (rewrite.get()) {
args.add("--rewrite")
}
if (bootstrapTags.get()) {
args.add(("--bootstrap-tags"))
}
return args.toList()
}
}
tasks.test {
useJUnitPlatform()
}
group = "io.papermc.paper"
version = "1.0-SNAPSHOT"

View File

@ -0,0 +1,23 @@
package io.papermc.generator;
import io.papermc.generator.registry.RegistryBootstrapper;
import io.papermc.generator.types.SourceGenerator;
import io.papermc.generator.types.craftblockdata.CraftBlockDataBootstrapper;
import io.papermc.generator.types.goal.MobGoalGenerator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import net.minecraft.Util;
import org.jspecify.annotations.NullMarked;
@NullMarked
public interface Generators {
List<SourceGenerator> API = Collections.unmodifiableList(Util.make(new ArrayList<>(), list -> {
RegistryBootstrapper.bootstrap(list);
list.add(new MobGoalGenerator("VanillaGoal", "com.destroystokyo.paper.entity.ai"));
// todo extract fields for registry based api
}));
List<SourceGenerator> SERVER = Collections.unmodifiableList(Util.make(new ArrayList<>(), CraftBlockDataBootstrapper::bootstrap));
}

View File

@ -0,0 +1,145 @@
package io.papermc.generator;
import com.google.common.util.concurrent.MoreExecutors;
import com.mojang.logging.LogUtils;
import io.papermc.generator.rewriter.registration.PaperPatternSourceSetRewriter;
import io.papermc.generator.rewriter.registration.PatternSourceSetRewriter;
import io.papermc.generator.types.SourceGenerator;
import io.papermc.generator.utils.experimental.ExperimentalCollector;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import net.minecraft.SharedConstants;
import net.minecraft.commands.Commands;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.LayeredRegistryAccess;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess;
import net.minecraft.resources.RegistryDataLoader;
import net.minecraft.server.Bootstrap;
import net.minecraft.server.RegistryLayer;
import net.minecraft.server.ReloadableServerResources;
import net.minecraft.server.packs.PackType;
import net.minecraft.server.packs.repository.Pack;
import net.minecraft.server.packs.repository.PackRepository;
import net.minecraft.server.packs.repository.ServerPacksSource;
import net.minecraft.server.packs.resources.MultiPackResourceManager;
import net.minecraft.tags.TagKey;
import net.minecraft.tags.TagLoader;
import net.minecraft.world.flag.FeatureFlags;
import org.apache.commons.io.file.PathUtils;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.slf4j.Logger;
import picocli.CommandLine;
@CommandLine.Command(
name = "generator",
description = "Rewrite and generate API classes and its implementation for Paper"
)
public class Main implements Callable<Integer> {
@CommandLine.Option(names = {"--sourceset"}, required = true)
Path sourceSet;
@CommandLine.Option(names = {"-cp", "--classpath"}, split = ":", required = true)
Set<Path> classpath;
@CommandLine.Option(names = {"--rewrite"})
boolean isRewrite;
@CommandLine.Option(names = {"--side"}, required = true)
String side;
@CommandLine.Option(names = {"--bootstrap-tags"})
boolean tagBootstrap;
private static final Logger LOGGER = LogUtils.getLogger();
public static RegistryAccess.@MonotonicNonNull Frozen REGISTRY_ACCESS;
public static @MonotonicNonNull Map<TagKey<?>, String> EXPERIMENTAL_TAGS;
public static CompletableFuture<Void> bootStrap(boolean withTags) {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
Bootstrap.validate();
PackRepository resourceRepository = ServerPacksSource.createVanillaTrustedRepository();
resourceRepository.reload();
MultiPackResourceManager resourceManager = new MultiPackResourceManager(PackType.SERVER_DATA, resourceRepository.getAvailablePacks().stream().map(Pack::open).toList());
LayeredRegistryAccess<RegistryLayer> layers = RegistryLayer.createRegistryAccess();
List<Registry.PendingTags<?>> pendingTags = TagLoader.loadTagsForExistingRegistries(resourceManager, layers.getLayer(RegistryLayer.STATIC));
List<HolderLookup.RegistryLookup<?>> worldGenLayer = TagLoader.buildUpdatedLookups(layers.getAccessForLoading(RegistryLayer.WORLDGEN), pendingTags);
RegistryAccess.Frozen frozenWorldgenRegistries = RegistryDataLoader.load(resourceManager, worldGenLayer, RegistryDataLoader.WORLDGEN_REGISTRIES);
layers = layers.replaceFrom(RegistryLayer.WORLDGEN, frozenWorldgenRegistries);
REGISTRY_ACCESS = layers.compositeAccess().freeze();
if (withTags) {
return ReloadableServerResources.loadResources(
resourceManager,
layers,
pendingTags,
FeatureFlags.VANILLA_SET,
Commands.CommandSelection.DEDICATED,
0,
MoreExecutors.directExecutor(),
MoreExecutors.directExecutor()
).whenComplete((result, ex) -> {
if (ex != null) {
resourceManager.close();
}
}).thenAccept(resources -> {
resources.updateStaticRegistryTags();
EXPERIMENTAL_TAGS = ExperimentalCollector.collectTags(resourceManager);
});
} else {
EXPERIMENTAL_TAGS = Map.of();
return CompletableFuture.completedFuture(null);
}
}
@Override
public Integer call() {
bootStrap(this.tagBootstrap).join();
try {
if (this.isRewrite) {
rewrite(this.sourceSet, this.classpath, this.side.equals("api") ? Rewriters.API : Rewriters.SERVER);
} else {
generate(this.sourceSet, this.side.equals("api") ? Generators.API : Generators.SERVER);
}
} catch (RuntimeException ex) {
throw ex;
} catch (Exception e) {
throw new RuntimeException(e);
}
return 0;
}
private static void rewrite(Path sourceSet, Set<Path> classpath, Consumer<PatternSourceSetRewriter> rewriters) throws IOException {
PatternSourceSetRewriter sourceSetRewriter = new PaperPatternSourceSetRewriter(classpath);
rewriters.accept(sourceSetRewriter);
sourceSetRewriter.apply(sourceSet.resolve("src/main/java"));
}
private static void generate(Path sourceSet, Collection<SourceGenerator> generators) throws IOException {
Path output = sourceSet.resolve("src/generated/java");
if (Files.exists(output)) {
PathUtils.deleteDirectory(output);
}
for (SourceGenerator generator : generators) {
generator.writeToFile(output);
}
LOGGER.info("Files written to {}", output.toAbsolutePath());
}
public static void main(String[] args) {
System.exit(new CommandLine(new Main()).execute(args));
}
}

View File

@ -0,0 +1,219 @@
package io.papermc.generator;
import io.papermc.generator.registry.RegistryBootstrapper;
import io.papermc.generator.registry.RegistryEntries;
import io.papermc.generator.rewriter.registration.PatternSourceSetRewriter;
import io.papermc.generator.rewriter.types.Types;
import io.papermc.generator.rewriter.types.registry.EnumRegistryRewriter;
import io.papermc.generator.rewriter.types.registry.FeatureFlagRewriter;
import io.papermc.generator.rewriter.types.registry.RegistryFieldRewriter;
import io.papermc.generator.rewriter.types.registry.RegistryTagRewriter;
import io.papermc.generator.rewriter.types.registry.TagRewriter;
import io.papermc.generator.rewriter.types.simple.BlockTypeRewriter;
import io.papermc.generator.rewriter.types.simple.CraftBlockDataMapping;
import io.papermc.generator.rewriter.types.simple.CraftBlockEntityStateMapping;
import io.papermc.generator.rewriter.types.simple.CraftPotionUtilRewriter;
import io.papermc.generator.rewriter.types.simple.EntityTypeRewriter;
import io.papermc.generator.rewriter.types.simple.MapPaletteRewriter;
import io.papermc.generator.rewriter.types.simple.MaterialRewriter;
import io.papermc.generator.rewriter.types.simple.MemoryKeyRewriter;
import io.papermc.generator.rewriter.types.registry.PaperFeatureFlagMapping;
import io.papermc.generator.rewriter.types.simple.StatisticRewriter;
import io.papermc.generator.rewriter.types.simple.trial.VillagerProfessionRewriter;
import io.papermc.generator.types.goal.MobGoalNames;
import io.papermc.generator.utils.Formatting;
import io.papermc.paper.datacomponent.item.consumable.ItemUseAnimation;
import io.papermc.typewriter.preset.EnumCloneRewriter;
import io.papermc.typewriter.preset.model.EnumValue;
import java.util.Map;
import java.util.function.Consumer;
import io.papermc.typewriter.replace.SearchMetadata;
import io.papermc.typewriter.replace.SearchReplaceRewriter;
import javax.lang.model.SourceVersion;
import net.kyori.adventure.text.format.NamedTextColor;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.Registries;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.item.Rarity;
import org.bukkit.Art;
import org.bukkit.FeatureFlag;
import org.bukkit.Fluid;
import org.bukkit.GameEvent;
import org.bukkit.JukeboxSong;
import org.bukkit.Material;
import org.bukkit.MusicInstrument;
import org.bukkit.Sound;
import org.bukkit.Statistic;
import org.bukkit.Tag;
import org.bukkit.block.Biome;
import org.bukkit.block.BlockType;
import org.bukkit.block.banner.PatternType;
import org.bukkit.damage.DamageType;
import org.bukkit.entity.Boat;
import org.bukkit.entity.Cat;
import org.bukkit.entity.Chicken;
import org.bukkit.entity.Cow;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Fox;
import org.bukkit.entity.Frog;
import org.bukkit.entity.Panda;
import org.bukkit.entity.Pig;
import org.bukkit.entity.Salmon;
import org.bukkit.entity.Sniffer;
import org.bukkit.entity.TropicalFish;
import org.bukkit.entity.Villager;
import org.bukkit.entity.Wolf;
import org.bukkit.entity.memory.MemoryKey;
import org.bukkit.generator.structure.Structure;
import org.bukkit.generator.structure.StructureType;
import org.bukkit.inventory.ItemRarity;
import org.bukkit.inventory.ItemType;
import org.bukkit.inventory.meta.trim.TrimMaterial;
import org.bukkit.inventory.meta.trim.TrimPattern;
import org.bukkit.inventory.recipe.CookingBookCategory;
import org.bukkit.inventory.recipe.CraftingBookCategory;
import org.bukkit.map.MapCursor;
import org.bukkit.map.MapPalette;
import org.bukkit.potion.PotionType;
import org.bukkit.scoreboard.DisplaySlot;
import org.bukkit.tag.DamageTypeTags;
import org.jspecify.annotations.NullMarked;
import static io.papermc.generator.rewriter.registration.PaperPatternSourceSetRewriter.composite;
import static io.papermc.generator.rewriter.registration.RewriterHolder.holder;
import static io.papermc.generator.utils.Formatting.quoted;
@NullMarked
public final class Rewriters {
public static void bootstrap(PatternSourceSetRewriter apiSourceSet, PatternSourceSetRewriter serverSourceSet) {
bootstrapApi(apiSourceSet);
bootstrapServer(serverSourceSet);
}
public static final Consumer<PatternSourceSetRewriter> API = Rewriters::bootstrapApi;
public static final Consumer<PatternSourceSetRewriter> SERVER = Rewriters::bootstrapServer;
private static void bootstrapApi(PatternSourceSetRewriter sourceSet) {
sourceSet
.register("PotionType", PotionType.class, new EnumRegistryRewriter<>(Registries.POTION))
.register("EntityType", EntityType.class, new EntityTypeRewriter())
.register("DisplaySlot", DisplaySlot.class, new EnumCloneRewriter<>(net.minecraft.world.scores.DisplaySlot.class) {
@Override
protected EnumValue.Builder rewriteEnumValue(net.minecraft.world.scores.DisplaySlot slot) {
final String name;
if (slot == net.minecraft.world.scores.DisplaySlot.LIST) {
name = "PLAYER_LIST";
} else {
name = Formatting.formatKeyAsField(slot.getSerializedName());
}
return EnumValue.builder(name).argument(quoted(slot.getSerializedName()));
}
})
.register("SnifferState", Sniffer.State.class, new EnumCloneRewriter<>(net.minecraft.world.entity.animal.sniffer.Sniffer.State.class))
.register("PandaGene", Panda.Gene.class, new EnumCloneRewriter<>(net.minecraft.world.entity.animal.Panda.Gene.class) {
@Override
protected EnumValue.Builder rewriteEnumValue(net.minecraft.world.entity.animal.Panda.Gene gene) {
return super.rewriteEnumValue(gene).argument(String.valueOf(gene.isRecessive()));
}
})
.register("CookingBookCategory", CookingBookCategory.class, new EnumCloneRewriter<>(net.minecraft.world.item.crafting.CookingBookCategory.class))
.register("CraftingBookCategory", CraftingBookCategory.class, new EnumCloneRewriter<>(net.minecraft.world.item.crafting.CraftingBookCategory.class))
.register("TropicalFishPattern", TropicalFish.Pattern.class, new EnumCloneRewriter<>(net.minecraft.world.entity.animal.TropicalFish.Pattern.class))
.register("BoatStatus", Boat.Status.class, new EnumCloneRewriter<>(net.minecraft.world.entity.vehicle.Boat.Status.class))
.register("FoxType", Fox.Type.class, new EnumCloneRewriter<>(net.minecraft.world.entity.animal.Fox.Variant.class))
.register("SalmonVariant", Salmon.Variant.class, new EnumCloneRewriter<>(net.minecraft.world.entity.animal.Salmon.Variant.class))
.register("ItemUseAnimation", ItemUseAnimation.class, new EnumCloneRewriter<>(net.minecraft.world.item.ItemUseAnimation.class))
.register("ItemRarity", ItemRarity.class, new EnumCloneRewriter<>(Rarity.class) {
@Override
protected EnumValue.Builder rewriteEnumValue(Rarity rarity) {
return super.rewriteEnumValue(rarity).argument(
"%s.%s".formatted(NamedTextColor.class.getSimpleName(), rarity.color().name())
);
}
})
.register(Material.class, composite(
holder("Blocks", new MaterialRewriter.Blocks()),
//holder("Material#isTransparent", MaterialRewriter.IsTransparent()),
holder("Items", new MaterialRewriter.Items())
))
.register(Statistic.class, composite(
holder("StatisticCustom", new StatisticRewriter.Custom()),
holder("StatisticType", new StatisticRewriter.Type())
))
.register(Villager.class, composite(
holder("VillagerType", Villager.Type.class, new RegistryFieldRewriter<>(Registries.VILLAGER_TYPE, "getType")),
holder("VillagerProfession", Villager.Profession.class, new VillagerProfessionRewriter())
))
.register("JukeboxSong", JukeboxSong.class, new RegistryFieldRewriter<>(Registries.JUKEBOX_SONG, "get") {
@Override
protected String rewriteFieldName(Holder.Reference<net.minecraft.world.item.JukeboxSong> reference) {
String keyedName = super.rewriteFieldName(reference);
if (!SourceVersion.isIdentifier(keyedName)) {
// fallback to field names for invalid identifier (happens for 5, 11, 13 etc.)
return RegistryEntries.byRegistryKey(Registries.JUKEBOX_SONG).getFieldNames().get(reference.key());
}
return keyedName;
}
})
.register("DamageTypeTags", DamageTypeTags.class, new RegistryTagRewriter<>(Registries.DAMAGE_TYPE, DamageType.class))
.register("MapCursorType", MapCursor.Type.class, new RegistryFieldRewriter<>(Registries.MAP_DECORATION_TYPE, "getType"))
.register("Structure", Structure.class, new RegistryFieldRewriter<>(Registries.STRUCTURE, "getStructure"))
.register("StructureType", StructureType.class, new RegistryFieldRewriter<>(Registries.STRUCTURE_TYPE, "getStructureType"))
.register("TrimPattern", TrimPattern.class, new RegistryFieldRewriter<>(Registries.TRIM_PATTERN, "getTrimPattern"))
.register("TrimMaterial", TrimMaterial.class, new RegistryFieldRewriter<>(Registries.TRIM_MATERIAL, "getTrimMaterial"))
.register("DamageType", DamageType.class, new RegistryFieldRewriter<>(Registries.DAMAGE_TYPE, "getDamageType"))
.register("GameEvent", GameEvent.class, new RegistryFieldRewriter<>(Registries.GAME_EVENT, "getEvent"))
.register("MusicInstrument", MusicInstrument.class, new RegistryFieldRewriter<>(Registries.INSTRUMENT, "getInstrument"))
.register("WolfVariant", Wolf.Variant.class, new RegistryFieldRewriter<>(Registries.WOLF_VARIANT, "getVariant"))
.register("WolfSoundVariant", Wolf.SoundVariant.class, new RegistryFieldRewriter<>(Registries.WOLF_SOUND_VARIANT, "getSoundVariant"))
.register("CatType", Cat.Type.class, new RegistryFieldRewriter<>(Registries.CAT_VARIANT, "getType"))
.register("FrogVariant", Frog.Variant.class, new RegistryFieldRewriter<>(Registries.FROG_VARIANT, "getVariant"))
.register("PatternType", PatternType.class, new RegistryFieldRewriter<>(Registries.BANNER_PATTERN, "getType"))
.register("Biome", Biome.class, new RegistryFieldRewriter<>(Registries.BIOME, "getBiome"))
.register("Fluid", Fluid.class, new RegistryFieldRewriter<>(Registries.FLUID, "getFluid"))
// .register("Attribute", Attribute.class, new RegistryFieldRewriter<>(Registries.ATTRIBUTE, "getAttribute")) - disable for now (javadocs)
.register("Sound", Sound.class, new RegistryFieldRewriter<>(Registries.SOUND_EVENT, "getSound"))
.register("Art", Art.class, new RegistryFieldRewriter<>(Registries.PAINTING_VARIANT, "getArt"))
.register("ChickenVariant", Chicken.Variant.class, new RegistryFieldRewriter<>(Registries.CHICKEN_VARIANT, "getVariant"))
.register("CowVariant", Cow.Variant.class, new RegistryFieldRewriter<>(Registries.COW_VARIANT, "getVariant"))
.register("PigVariant", Pig.Variant.class, new RegistryFieldRewriter<>(Registries.PIG_VARIANT, "getVariant"))
.register("MemoryKey", MemoryKey.class, new MemoryKeyRewriter())
// .register("DataComponentTypes", DataComponentTypes.class, new DataComponentTypesRewriter()) - disable for now
// .register("ItemType", ItemType.class, new ItemTypeRewriter()) - disable for now, lynx want the generic type
.register("BlockType", BlockType.class, new BlockTypeRewriter())
.register("FeatureFlag", FeatureFlag.class, new FeatureFlagRewriter())
.register("Tag", Tag.class, new TagRewriter())
.register("MapPalette#colors", MapPalette.class, new MapPaletteRewriter());
RegistryBootstrapper.bootstrapApi(sourceSet);
}
private static void bootstrapServer(PatternSourceSetRewriter sourceSet) {
sourceSet
.register("CraftBlockData#MAP", Types.CRAFT_BLOCK_DATA, new CraftBlockDataMapping())
.register("CraftBlockEntityStates", Types.CRAFT_BLOCK_STATES, new CraftBlockEntityStateMapping())
.register(Types.CRAFT_STATISTIC, composite(
holder("CraftStatisticCustom", new StatisticRewriter.CraftCustom()),
holder("CraftStatisticType", new StatisticRewriter.CraftType())
))
.register(Types.CRAFT_POTION_UTIL, composite(
holder("CraftPotionUtil#upgradeable", new CraftPotionUtilRewriter("strong")),
holder("CraftPotionUtil#extendable", new CraftPotionUtilRewriter("long"))
))
.register("PaperFeatureFlagProviderImpl#FLAGS", Types.PAPER_FEATURE_FLAG_PROVIDER_IMPL, new PaperFeatureFlagMapping())
.register("MobGoalHelper#bukkitMap", Types.MOB_GOAL_HELPER, new SearchReplaceRewriter() {
@Override
protected void insert(SearchMetadata metadata, StringBuilder builder) {
for (Map.Entry<Class<? extends Mob>, Class<? extends org.bukkit.entity.Mob>> entry : MobGoalNames.bukkitMap.entrySet()) {
builder.append(metadata.indent()).append("bukkitMap.put(%s.class, %s.class);".formatted(
entry.getKey().getCanonicalName(), this.importCollector.getShortName(entry.getValue())
));
builder.append('\n');
}
}
});
RegistryBootstrapper.bootstrapServer(sourceSet);
}
}

View File

@ -0,0 +1,45 @@
package io.papermc.generator.registry;
import io.papermc.generator.rewriter.registration.PatternSourceSetRewriter;
import io.papermc.generator.rewriter.types.Types;
import io.papermc.generator.rewriter.types.registry.PaperRegistriesRewriter;
import io.papermc.generator.rewriter.types.registry.RegistryEventsRewriter;
import io.papermc.generator.types.SourceGenerator;
import io.papermc.generator.types.registry.GeneratedKeyType;
import io.papermc.generator.types.registry.GeneratedTagKeyType;
import io.papermc.paper.registry.event.RegistryEvents;
import java.util.List;
import net.minecraft.core.registries.Registries;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class RegistryBootstrapper {
private static final String PAPER_REGISTRY_PACKAGE = "io.papermc.paper.registry";
public static void bootstrap(List<SourceGenerator> generators) {
// typed/tag keys
RegistryEntries.forEach(entry -> {
generators.add(new GeneratedKeyType<>(PAPER_REGISTRY_PACKAGE + ".keys", entry));
if (entry.registry().listTags().findAny().isPresent()) {
generators.add(new GeneratedTagKeyType(entry, PAPER_REGISTRY_PACKAGE + ".keys.tags"));
}
});
// todo remove once entity type is a registry
generators.add(new GeneratedTagKeyType(RegistryEntries.byRegistryKey(Registries.ENTITY_TYPE), PAPER_REGISTRY_PACKAGE + ".keys.tags"));
}
public static void bootstrap(PatternSourceSetRewriter apiSourceSet, PatternSourceSetRewriter serverSourceSet) {
bootstrapApi(apiSourceSet);
bootstrapServer(serverSourceSet);
}
public static void bootstrapApi(PatternSourceSetRewriter sourceSet) {
sourceSet.register("RegistryEvents", RegistryEvents.class, new RegistryEventsRewriter());
}
public static void bootstrapServer(PatternSourceSetRewriter sourceSet) {
sourceSet.register("RegistryDefinitions", Types.PAPER_REGISTRIES, new PaperRegistriesRewriter());
}
}

View File

@ -0,0 +1,215 @@
package io.papermc.generator.registry;
import io.papermc.generator.utils.ClassHelper;
import io.papermc.paper.datacomponent.DataComponentType;
import io.papermc.paper.datacomponent.DataComponentTypes;
import io.papermc.paper.registry.data.BannerPatternRegistryEntry;
import io.papermc.paper.registry.data.DamageTypeRegistryEntry;
import io.papermc.paper.registry.data.EnchantmentRegistryEntry;
import io.papermc.paper.registry.data.GameEventRegistryEntry;
import io.papermc.paper.registry.data.PaintingVariantRegistryEntry;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import net.minecraft.core.Registry;
import net.minecraft.core.component.DataComponents;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.world.damagesource.DamageTypes;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.animal.CatVariants;
import net.minecraft.world.entity.animal.ChickenVariants;
import net.minecraft.world.entity.animal.CowVariants;
import net.minecraft.world.entity.animal.PigVariants;
import net.minecraft.world.entity.animal.frog.FrogVariants;
import net.minecraft.world.entity.animal.wolf.WolfSoundVariants;
import net.minecraft.world.entity.animal.wolf.WolfVariants;
import net.minecraft.world.entity.decoration.PaintingVariants;
import net.minecraft.world.entity.npc.VillagerProfession;
import net.minecraft.world.entity.npc.VillagerType;
import net.minecraft.world.item.Instruments;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.JukeboxSongs;
import net.minecraft.world.item.alchemy.Potions;
import net.minecraft.world.item.enchantment.Enchantments;
import net.minecraft.world.item.equipment.trim.TrimMaterials;
import net.minecraft.world.item.equipment.trim.TrimPatterns;
import net.minecraft.world.level.biome.Biomes;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BannerPatterns;
import net.minecraft.world.level.levelgen.structure.BuiltinStructures;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.level.saveddata.maps.MapDecorationTypes;
import org.bukkit.Art;
import org.bukkit.Fluid;
import org.bukkit.GameEvent;
import org.bukkit.JukeboxSong;
import org.bukkit.Keyed;
import org.bukkit.MusicInstrument;
import org.bukkit.Particle;
import org.bukkit.Sound;
import org.bukkit.attribute.Attribute;
import org.bukkit.block.Biome;
import org.bukkit.block.BlockType;
import org.bukkit.block.banner.PatternType;
import org.bukkit.damage.DamageType;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Cat;
import org.bukkit.entity.Chicken;
import org.bukkit.entity.Cow;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Frog;
import org.bukkit.entity.Pig;
import org.bukkit.entity.Villager;
import org.bukkit.entity.Wolf;
import org.bukkit.entity.memory.MemoryKey;
import org.bukkit.generator.structure.Structure;
import org.bukkit.generator.structure.StructureType;
import org.bukkit.inventory.ItemType;
import org.bukkit.inventory.MenuType;
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.bukkit.potion.PotionType;
import org.jspecify.annotations.NullMarked;
@NullMarked
public final class RegistryEntries {
// CraftBukkit entry where implementation start by "Craft"
private static <T> RegistryEntry<T> entry(ResourceKey<? extends Registry<T>> registryKey, Class<?> holderElementsClass, Class<? extends Keyed> apiClass) {
return entry(registryKey, holderElementsClass, apiClass, "Craft");
}
private static <T> RegistryEntry<T> entry(ResourceKey<? extends Registry<T>> registryKey, Class<?> holderElementsClass, Class<? extends Keyed> apiClass, String implPrefix) {
String name = io.papermc.typewriter.util.ClassHelper.retrieveFullNestedName(apiClass);
RegistryKeyField<T> registryKeyField = (RegistryKeyField<T>) REGISTRY_KEY_FIELDS.get(registryKey);
String[] classes = name.split("\\.");
if (classes.length == 0) {
return new RegistryEntry<>(registryKey, registryKeyField, holderElementsClass, apiClass, implPrefix.concat(apiClass.getSimpleName()));
}
String implName = Arrays.stream(classes).map(implPrefix::concat).collect(Collectors.joining("."));
return new RegistryEntry<>(registryKey, registryKeyField, holderElementsClass, apiClass, implName);
}
@Deprecated
private static <T> RegistryEntry<T> inconsistentEntry(ResourceKey<? extends Registry<T>> registryKey, Class<?> holderElementsClass, Class<? extends Keyed> apiClass, String implClass) {
return new RegistryEntry<>(registryKey, (RegistryKeyField<T>) REGISTRY_KEY_FIELDS.get(registryKey), holderElementsClass, apiClass, implClass);
}
private static final Map<ResourceKey<? extends Registry<?>>, RegistryKeyField<?>> REGISTRY_KEY_FIELDS;
static {
Map<ResourceKey<? extends Registry<?>>, RegistryKeyField<?>> registryKeyFields = new IdentityHashMap<>();
try {
for (Field field : Registries.class.getDeclaredFields()) {
if (!ResourceKey.class.isAssignableFrom(field.getType())) {
continue;
}
if (ClassHelper.isStaticConstant(field, Modifier.PUBLIC)) {
Type elementType = ClassHelper.getNestedTypeParameter(field.getGenericType(), ResourceKey.class, Registry.class, null);
if (elementType != null) {
registryKeyFields.put(((ResourceKey<? extends Registry<?>>) field.get(null)), new RegistryKeyField<>(ClassHelper.eraseType(elementType), field.getName()));
}
}
}
} catch (ReflectiveOperationException ex) {
throw new RuntimeException(ex);
}
REGISTRY_KEY_FIELDS = Collections.unmodifiableMap(registryKeyFields);
}
public static final Set<Class<?>> REGISTRY_CLASS_NAME_BASED_ON_API = Set.of(
BlockType.class,
ItemType.class
);
public static final List<RegistryEntry<?>> BUILT_IN = List.of(
entry(Registries.GAME_EVENT, net.minecraft.world.level.gameevent.GameEvent.class, GameEvent.class).apiRegistryBuilder(GameEventRegistryEntry.Builder.class, "PaperGameEventRegistryEntry.PaperBuilder"),
entry(Registries.STRUCTURE_TYPE, net.minecraft.world.level.levelgen.structure.StructureType.class, StructureType.class),
entry(Registries.MOB_EFFECT, MobEffects.class, PotionEffectType.class),
entry(Registries.BLOCK, Blocks.class, BlockType.class),
entry(Registries.ITEM, Items.class, ItemType.class),
entry(Registries.VILLAGER_PROFESSION, VillagerProfession.class, Villager.Profession.class),
entry(Registries.VILLAGER_TYPE, VillagerType.class, Villager.Type.class),
entry(Registries.MAP_DECORATION_TYPE, MapDecorationTypes.class, MapCursor.Type.class),
entry(Registries.MENU, net.minecraft.world.inventory.MenuType.class, MenuType.class),
entry(Registries.ATTRIBUTE, Attributes.class, Attribute.class),
entry(Registries.FLUID, Fluids.class, Fluid.class),
entry(Registries.SOUND_EVENT, SoundEvents.class, Sound.class).allowDirect().apiRegistryField("SOUNDS"),
entry(Registries.DATA_COMPONENT_TYPE, DataComponents.class, DataComponentType.class, "Paper").preload(DataComponentTypes.class).apiAccessName("of")
);
public static final List<RegistryEntry<?>> DATA_DRIVEN = List.of(
entry(Registries.BIOME, Biomes.class, Biome.class).delayed(),
entry(Registries.STRUCTURE, BuiltinStructures.class, Structure.class).delayed(),
entry(Registries.TRIM_MATERIAL, TrimMaterials.class, TrimMaterial.class).allowDirect().delayed(),
entry(Registries.TRIM_PATTERN, TrimPatterns.class, TrimPattern.class).allowDirect().delayed(),
entry(Registries.DAMAGE_TYPE, DamageTypes.class, DamageType.class).apiRegistryBuilder(DamageTypeRegistryEntry.Builder.class, "PaperDamageTypeRegistryEntry.PaperBuilder").delayed(),
entry(Registries.WOLF_VARIANT, WolfVariants.class, Wolf.Variant.class).delayed(),
entry(Registries.WOLF_SOUND_VARIANT, WolfSoundVariants.class, Wolf.SoundVariant.class),
entry(Registries.ENCHANTMENT, Enchantments.class, Enchantment.class).apiRegistryBuilder(EnchantmentRegistryEntry.Builder.class, "PaperEnchantmentRegistryEntry.PaperBuilder").serializationUpdater("ENCHANTMENT_RENAME").delayed(),
entry(Registries.JUKEBOX_SONG, JukeboxSongs.class, JukeboxSong.class).delayed(),
entry(Registries.BANNER_PATTERN, BannerPatterns.class, PatternType.class).allowDirect().apiRegistryBuilder(BannerPatternRegistryEntry.Builder.class, "PaperBannerPatternRegistryEntry.PaperBuilder").delayed(),
entry(Registries.PAINTING_VARIANT, PaintingVariants.class, Art.class).allowDirect().apiRegistryBuilder(PaintingVariantRegistryEntry.Builder.class, "PaperPaintingVariantRegistryEntry.PaperBuilder").apiRegistryField("ART").delayed(),
entry(Registries.INSTRUMENT, Instruments.class, MusicInstrument.class).allowDirect().delayed(),
entry(Registries.CAT_VARIANT, CatVariants.class, Cat.Type.class).delayed(),
entry(Registries.FROG_VARIANT, FrogVariants.class, Frog.Variant.class).delayed(),
entry(Registries.CHICKEN_VARIANT, ChickenVariants.class, Chicken.Variant.class),
entry(Registries.COW_VARIANT, CowVariants.class, Cow.Variant.class),
entry(Registries.PIG_VARIANT, PigVariants.class, Pig.Variant.class)
);
public static final List<RegistryEntry<?>> API_ONLY = List.of(
entry(Registries.ENTITY_TYPE, net.minecraft.world.entity.EntityType.class, EntityType.class),
entry(Registries.PARTICLE_TYPE, ParticleTypes.class, Particle.class),
entry(Registries.POTION, Potions.class, PotionType.class),
entry(Registries.MEMORY_MODULE_TYPE, MemoryModuleType.class, MemoryKey.class)
);
public static final Map<ResourceKey<? extends Registry<?>>, RegistryEntry<?>> BY_REGISTRY_KEY;
static {
Map<ResourceKey<? extends Registry<?>>, RegistryEntry<?>> byRegistryKey = new IdentityHashMap<>(BUILT_IN.size() + DATA_DRIVEN.size() + API_ONLY.size());
forEach(entry -> {
byRegistryKey.put(entry.registryKey(), entry);
}, RegistryEntries.BUILT_IN, RegistryEntries.DATA_DRIVEN, RegistryEntries.API_ONLY);
BY_REGISTRY_KEY = Collections.unmodifiableMap(byRegistryKey);
}
@SuppressWarnings("unchecked")
public static <T> RegistryEntry<T> byRegistryKey(ResourceKey<? extends Registry<T>> registryKey) {
return (RegistryEntry<T>) Objects.requireNonNull(BY_REGISTRY_KEY.get(registryKey));
}
// real registries
public static void forEach(Consumer<RegistryEntry<?>> callback) {
forEach(callback, RegistryEntries.BUILT_IN, RegistryEntries.DATA_DRIVEN);
}
@SafeVarargs
public static void forEach(Consumer<RegistryEntry<?>> callback, List<RegistryEntry<?>>... datas) {
for (List<RegistryEntry<?>> data : datas) {
for (RegistryEntry<?> entry : data) {
callback.accept(entry);
}
}
}
private RegistryEntries() {
}
}

View File

@ -0,0 +1,214 @@
package io.papermc.generator.registry;
import com.google.common.base.Preconditions;
import io.papermc.generator.Main;
import io.papermc.generator.utils.ClassHelper;
import java.lang.constant.ConstantDescs;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import javax.lang.model.SourceVersion;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceKey;
import org.bukkit.Keyed;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public final class RegistryEntry<T> {
private final ResourceKey<? extends Registry<T>> registryKey;
private final RegistryKeyField<T> registryKeyField;
private final Class<T> elementClass;
private final Class<?> holderElementsClass;
private boolean allowDirect;
private final Class<? extends Keyed> apiClass; // TODO remove Keyed
private Class<?> preloadClass;
private final String implClass;
private @Nullable Class<?> apiRegistryBuilder;
private @Nullable String apiRegistryBuilderImpl;
private @Nullable String fieldRename;
private boolean delayed;
private String apiAccessName = ConstantDescs.INIT_NAME;
private Optional<String> apiRegistryField = Optional.empty();
private @Nullable Map<ResourceKey<T>, String> fieldNames;
public RegistryEntry(ResourceKey<? extends Registry<T>> registryKey, RegistryKeyField<T> registryKeyField, Class<?> holderElementsClass, Class<? extends Keyed> apiClass, String implClass) {
this.registryKey = registryKey;
this.registryKeyField = registryKeyField;
this.elementClass = registryKeyField.elementClass();
this.holderElementsClass = holderElementsClass;
this.apiClass = apiClass;
this.preloadClass = apiClass;
this.implClass = implClass;
}
public ResourceKey<? extends Registry<T>> registryKey() {
return this.registryKey;
}
public Registry<T> registry() {
return Main.REGISTRY_ACCESS.lookupOrThrow(this.registryKey);
}
public String registryKeyField() {
return this.registryKeyField.name();
}
public Class<? extends Keyed> apiClass() {
return this.apiClass;
}
public String implClass() {
return this.implClass;
}
public RegistryEntry<T> allowDirect() {
this.allowDirect = true;
return this;
}
public RegistryEntry<T> delayed() {
this.delayed = true;
return this;
}
public RegistryEntry<T> preload(Class<?> klass) {
this.preloadClass = klass;
return this;
}
public RegistryEntry<T> apiAccessName(String name) {
Preconditions.checkArgument(SourceVersion.isIdentifier(name) && !SourceVersion.isKeyword(name), "Invalid accessor name");
this.apiAccessName = name;
return this;
}
public RegistryEntry<T> serializationUpdater(String fieldName) {
this.fieldRename = fieldName;
return this;
}
public boolean canAllowDirect() {
return this.allowDirect;
}
public boolean isDelayed() {
return this.delayed;
}
public String apiAccessName() {
return this.apiAccessName;
}
public Class<?> preloadClass() {
return this.preloadClass;
}
public @Nullable String fieldRename() {
return this.fieldRename;
}
public @Nullable Class<?> apiRegistryBuilder() {
return this.apiRegistryBuilder;
}
public @Nullable String apiRegistryBuilderImpl() {
return this.apiRegistryBuilderImpl;
}
public RegistryEntry<T> apiRegistryBuilder(Class<?> builderClass, String builderImplClass) {
this.apiRegistryBuilder = builderClass;
this.apiRegistryBuilderImpl = builderImplClass;
return this;
}
public Optional<String> apiRegistryField() {
return this.apiRegistryField;
}
public RegistryEntry<T> apiRegistryField(String registryField) {
this.apiRegistryField = Optional.of(registryField);
return this;
}
public String keyClassName() {
if (RegistryEntries.REGISTRY_CLASS_NAME_BASED_ON_API.contains(this.apiClass)) {
return this.apiClass.getSimpleName();
}
return this.elementClass.getSimpleName();
}
public boolean allowCustomKeys() {
return this.apiRegistryBuilder != null || RegistryEntries.DATA_DRIVEN.contains(this);
}
private <TO> Map<ResourceKey<T>, TO> getFields(Map<ResourceKey<T>, TO> map, Function<Field, @Nullable TO> transform) {
Registry<T> registry = this.registry();
try {
for (Field field : this.holderElementsClass.getDeclaredFields()) {
if (!ResourceKey.class.isAssignableFrom(field.getType()) && !Holder.Reference.class.isAssignableFrom(field.getType()) && !this.elementClass.isAssignableFrom(field.getType())) {
continue;
}
if (ClassHelper.isStaticConstant(field, Modifier.PUBLIC)) {
ResourceKey<T> key = null;
if (this.elementClass.isAssignableFrom(field.getType())) {
key = registry.getResourceKey(this.elementClass.cast(field.get(null))).orElseThrow();
} else {
if (field.getGenericType() instanceof ParameterizedType complexType && complexType.getActualTypeArguments().length == 1 &&
complexType.getActualTypeArguments()[0] == this.elementClass) {
if (Holder.Reference.class.isAssignableFrom(field.getType())) {
key = ((Holder.Reference<T>) field.get(null)).key();
} else {
key = (ResourceKey<T>) field.get(null);
}
}
}
if (key != null) {
TO value = transform.apply(field);
if (value != null) {
map.put(key, value);
}
}
}
}
} catch (ReflectiveOperationException ex) {
throw new RuntimeException(ex);
}
return map;
}
public Map<ResourceKey<T>, String> getFieldNames() {
if (this.fieldNames == null) {
this.fieldNames = this.getFields(Field::getName);
}
return this.fieldNames;
}
public <TO> Map<ResourceKey<T>, TO> getFields(Function<Field, @Nullable TO> transform) {
return Collections.unmodifiableMap(this.getFields(new IdentityHashMap<>(), transform));
}
@Override
public String toString() {
return "RegistryEntry[" +
"registryKey=" + this.registryKey + ", " +
"registryKeyField=" + this.registryKeyField + ", " +
"apiClass=" + this.apiClass + ", " +
"implClass=" + this.implClass + ", " +
']';
}
}

View File

@ -0,0 +1,7 @@
package io.papermc.generator.registry;
import org.jspecify.annotations.NullMarked;
@NullMarked
public record RegistryKeyField<T>(Class<T> elementClass, String name) {
}

View File

@ -0,0 +1,71 @@
package io.papermc.generator.rewriter.registration;
import io.papermc.generator.rewriter.utils.Annotations;
import io.papermc.generator.types.SimpleGenerator;
import io.papermc.paper.generated.GeneratedFrom;
import io.papermc.typewriter.ClassNamed;
import io.papermc.typewriter.SourceFile;
import io.papermc.typewriter.context.IndentUnit;
import io.papermc.typewriter.context.SourcesMetadata;
import io.papermc.typewriter.registration.SourceSetRewriterImpl;
import io.papermc.typewriter.replace.CompositeRewriter;
import io.papermc.typewriter.replace.ReplaceOptions;
import io.papermc.typewriter.replace.ReplaceOptionsLike;
import io.papermc.typewriter.replace.SearchReplaceRewriter;
import java.io.File;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
import net.minecraft.SharedConstants;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.VisibleForTesting;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public class PaperPatternSourceSetRewriter extends SourceSetRewriterImpl<PatternSourceSetRewriter> implements PatternSourceSetRewriter {
private static final String COMMENT_MARKER_FORMAT = "%s generate - %s"; // {0} = Start|End {1} = pattern
private static final IndentUnit INDENT_UNIT = IndentUnit.parse(SimpleGenerator.INDENT_UNIT);
public PaperPatternSourceSetRewriter() {
this(Collections.emptySet());
}
public PaperPatternSourceSetRewriter(Set<Path> classpath) {
super(SourcesMetadata.of(INDENT_UNIT, b -> b.classpath(classpath))); // let the runtime java version since it will always be in sync with what paperweight use
}
@VisibleForTesting
public SourcesMetadata getMetadata() {
return this.metadata;
}
private static ReplaceOptionsLike getOptions(String pattern, @Nullable ClassNamed targetClass) {
return ReplaceOptions.between(
COMMENT_MARKER_FORMAT.formatted("Start", pattern),
COMMENT_MARKER_FORMAT.formatted("End", pattern)
)
.generatedComment(Annotations.annotationStyle(GeneratedFrom.class) + " " + SharedConstants.getCurrentVersion().getId())
.targetClass(targetClass);
}
@Override
public PatternSourceSetRewriter register(String pattern, ClassNamed targetClass, SearchReplaceRewriter rewriter) {
return super.register(SourceFile.of(targetClass.topLevel()), rewriter.withOptions(getOptions(pattern, targetClass)).customName(pattern));
}
@Override
public PatternSourceSetRewriter register(ClassNamed mainClass, CompositeRewriter rewriter) {
return super.register(SourceFile.of(mainClass), rewriter);
}
@Contract(value = "_ -> new", pure = true)
public static CompositeRewriter composite(RewriterHolder... holders) {
return CompositeRewriter.bind(Arrays.stream(holders)
.map(holder -> holder.transform(PaperPatternSourceSetRewriter::getOptions))
.toArray(SearchReplaceRewriter[]::new));
}
}

View File

@ -0,0 +1,23 @@
package io.papermc.generator.rewriter.registration;
import io.papermc.typewriter.ClassNamed;
import io.papermc.typewriter.registration.SourceSetRewriter;
import io.papermc.typewriter.replace.CompositeRewriter;
import io.papermc.typewriter.replace.SearchReplaceRewriter;
import org.jspecify.annotations.NullMarked;
@NullMarked
public interface PatternSourceSetRewriter extends SourceSetRewriter<PatternSourceSetRewriter> {
default PatternSourceSetRewriter register(String pattern, Class<?> targetClass, SearchReplaceRewriter rewriter) {
return register(pattern, new ClassNamed(targetClass), rewriter);
}
PatternSourceSetRewriter register(String pattern, ClassNamed targetClass, SearchReplaceRewriter rewriter);
default PatternSourceSetRewriter register(Class<?> mainClass, CompositeRewriter rewriter) {
return this.register(new ClassNamed(mainClass), rewriter);
}
PatternSourceSetRewriter register(ClassNamed mainClass, CompositeRewriter rewriter);
}

View File

@ -0,0 +1,27 @@
package io.papermc.generator.rewriter.registration;
import io.papermc.typewriter.ClassNamed;
import io.papermc.typewriter.replace.ReplaceOptionsLike;
import io.papermc.typewriter.replace.SearchReplaceRewriter;
import java.util.function.BiFunction;
import org.jetbrains.annotations.Contract;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public record RewriterHolder(String pattern, @Nullable ClassNamed targetClass, SearchReplaceRewriter rewriter) {
@Contract(value = "_, _, _ -> new", pure = true)
public static RewriterHolder holder(String pattern, @Nullable Class<?> targetClass, SearchReplaceRewriter rewriter) {
return new RewriterHolder(pattern, targetClass == null ? null : new ClassNamed(targetClass), rewriter);
}
@Contract(value = "_, _ -> new", pure = true)
public static RewriterHolder holder(String pattern, SearchReplaceRewriter rewriter) {
return holder(pattern, null, rewriter);
}
public SearchReplaceRewriter transform(BiFunction<String, @Nullable ClassNamed, ReplaceOptionsLike> patternMapper) {
return this.rewriter.withOptions(patternMapper.apply(this.pattern, this.targetClass)).customName(this.pattern);
}
}

View File

@ -0,0 +1,28 @@
package io.papermc.generator.rewriter.types;
import io.papermc.typewriter.ClassNamed;
import org.jspecify.annotations.NullMarked;
@NullMarked
public final class Types {
public static final String BASE_PACKAGE = "org.bukkit.craftbukkit";
public static final ClassNamed CRAFT_BLOCK_DATA = ClassNamed.of(BASE_PACKAGE + ".block.data", "CraftBlockData");
public static final ClassNamed CRAFT_BLOCK_STATES = ClassNamed.of(BASE_PACKAGE + ".block", "CraftBlockStates");
public static final ClassNamed CRAFT_STATISTIC = ClassNamed.of(BASE_PACKAGE, "CraftStatistic");
public static final ClassNamed CRAFT_POTION_UTIL = ClassNamed.of(BASE_PACKAGE + ".potion", "CraftPotionUtil");
public static final ClassNamed FIELD_RENAME = ClassNamed.of(BASE_PACKAGE + ".legacy", "FieldRename");
public static final ClassNamed PAPER_REGISTRIES = ClassNamed.of("io.papermc.paper.registry", "PaperRegistries");
public static final ClassNamed PAPER_FEATURE_FLAG_PROVIDER_IMPL = ClassNamed.of("io.papermc.paper.world.flag", "PaperFeatureFlagProviderImpl");
public static final ClassNamed PAPER_SIMPLE_REGISTRY = ClassNamed.of("io.papermc.paper.registry", "PaperSimpleRegistry");
public static final ClassNamed MOB_GOAL_HELPER = ClassNamed.of("com.destroystokyo.paper.entity.ai", "MobGoalHelper");
}

View File

@ -0,0 +1,82 @@
package io.papermc.generator.rewriter.types.registry;
import com.google.common.base.Suppliers;
import io.papermc.generator.Main;
import io.papermc.generator.rewriter.utils.Annotations;
import io.papermc.generator.utils.Formatting;
import io.papermc.generator.utils.experimental.ExperimentalCollector;
import io.papermc.generator.utils.experimental.SingleFlagHolder;
import io.papermc.typewriter.preset.EnumRewriter;
import io.papermc.typewriter.preset.model.EnumValue;
import java.util.Map;
import java.util.function.Supplier;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.flag.FeatureElement;
import net.minecraft.world.flag.FeatureFlags;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import static io.papermc.generator.utils.Formatting.quoted;
@NullMarked
@ApiStatus.Obsolete
public class EnumRegistryRewriter<T> extends EnumRewriter<Holder.Reference<T>> {
private final Supplier<Registry<T>> registry;
private final Supplier<Map<ResourceKey<T>, SingleFlagHolder>> experimentalKeys;
private final boolean isFilteredRegistry;
private final boolean hasKeyArgument;
public EnumRegistryRewriter(ResourceKey<? extends Registry<T>> registryKey) {
this(registryKey, true);
}
protected EnumRegistryRewriter(ResourceKey<? extends Registry<T>> registryKey, boolean hasKeyArgument) {
this.registry = Suppliers.memoize(() -> Main.REGISTRY_ACCESS.lookupOrThrow(registryKey));
this.experimentalKeys = Suppliers.memoize(() -> ExperimentalCollector.collectDataDrivenElementIds(this.registry.get()));
this.isFilteredRegistry = FeatureElement.FILTERED_REGISTRIES.contains(registryKey);
this.hasKeyArgument = hasKeyArgument;
}
@Override
protected Iterable<Holder.Reference<T>> getValues() {
return this.registry.get().listElements().sorted(Formatting.alphabeticKeyOrder(reference -> reference.key().location().getPath()))::iterator;
}
@Override
protected EnumValue.Builder rewriteEnumValue(Holder.Reference<T> reference) {
EnumValue.Builder value = EnumValue.builder(Formatting.formatKeyAsField(reference.key().location().getPath()));
if (this.hasKeyArgument) {
value.argument(quoted(reference.key().location().getPath()));
}
return value;
}
@Override
protected void appendEnumValue(Holder.Reference<T> reference, StringBuilder builder, String indent, boolean reachEnd) {
// experimental annotation
SingleFlagHolder requiredFeature = this.getRequiredFeature(reference);
if (requiredFeature != null) {
Annotations.experimentalAnnotations(builder, indent, this.importCollector, requiredFeature);
}
super.appendEnumValue(reference, builder, indent, reachEnd);
}
protected @Nullable SingleFlagHolder getRequiredFeature(Holder.Reference<T> reference) {
if (this.isFilteredRegistry) {
// built-in registry
FeatureElement element = (FeatureElement) reference.value();
if (FeatureFlags.isExperimental(element.requiredFeatures())) {
return SingleFlagHolder.fromSet(element.requiredFeatures());
}
} else {
// data-driven registry
return this.experimentalKeys.get().get(reference.key());
}
return null;
}
}

View File

@ -0,0 +1,61 @@
package io.papermc.generator.rewriter.types.registry;
import com.mojang.logging.LogUtils;
import io.papermc.generator.utils.Formatting;
import io.papermc.typewriter.SourceFile;
import io.papermc.typewriter.replace.SearchMetadata;
import io.papermc.typewriter.replace.SearchReplaceRewriter;
import java.util.Iterator;
import java.util.Map;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.flag.FeatureFlag;
import net.minecraft.world.flag.FeatureFlagSet;
import net.minecraft.world.flag.FeatureFlags;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
import org.slf4j.Logger;
import static io.papermc.generator.rewriter.utils.Annotations.annotation;
import static io.papermc.generator.utils.Formatting.quoted;
@NullMarked
public class FeatureFlagRewriter extends SearchReplaceRewriter {
private static final Logger LOGGER = LogUtils.getLogger();
@Override
public boolean registerFor(SourceFile file) {
try {
org.bukkit.FeatureFlag.class.getDeclaredMethod("create", String.class);
} catch (NoSuchMethodException e) {
LOGGER.error("Fetch method not found, skipping the rewriter for feature flag", e);
return false;
}
return super.registerFor(file);
}
@Override
protected void insert(SearchMetadata metadata, StringBuilder builder) {
Iterator<Map.Entry<ResourceLocation, FeatureFlag>> flagIterator = FeatureFlags.REGISTRY.names.entrySet().stream().sorted(Formatting.alphabeticKeyOrder(entry -> entry.getKey().getPath())).iterator();
while (flagIterator.hasNext()) {
Map.Entry<ResourceLocation, FeatureFlag> entry = flagIterator.next();
ResourceLocation name = entry.getKey();
if (FeatureFlags.isExperimental(FeatureFlagSet.of(entry.getValue()))) {
builder.append(metadata.indent()).append(annotation(ApiStatus.Experimental.class, this.importCollector)).append('\n');
}
builder.append(metadata.indent());
builder.append(org.bukkit.FeatureFlag.class.getSimpleName()).append(' ').append(Formatting.formatKeyAsField(name.getPath()));
builder.append(" = ");
builder.append("create(%s)".formatted(quoted(name.getPath())));
builder.append(';');
builder.append('\n');
if (flagIterator.hasNext()) {
builder.append('\n');
}
}
}
}

View File

@ -0,0 +1,30 @@
package io.papermc.generator.rewriter.types.registry;
import io.papermc.generator.utils.Formatting;
import io.papermc.typewriter.replace.SearchMetadata;
import io.papermc.typewriter.replace.SearchReplaceRewriter;
import java.util.Iterator;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.flag.FeatureFlags;
import org.bukkit.FeatureFlag;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class PaperFeatureFlagMapping extends SearchReplaceRewriter {
@Override
protected void insert(SearchMetadata metadata, StringBuilder builder) {
Iterator<ResourceLocation> flagIterator = FeatureFlags.REGISTRY.toNames(FeatureFlags.REGISTRY.allFlags()).stream().sorted(Formatting.alphabeticKeyOrder(ResourceLocation::getPath)).iterator();
while (flagIterator.hasNext()) {
ResourceLocation name = flagIterator.next();
String keyedName = Formatting.formatKeyAsField(name.getPath());
builder.append(metadata.indent());
builder.append("%s.%s, %s.%s".formatted(FeatureFlag.class.getSimpleName(), keyedName, FeatureFlags.class.getSimpleName(), keyedName));
if (flagIterator.hasNext()) {
builder.append(',');
}
builder.append('\n');
}
}
}

View File

@ -0,0 +1,105 @@
package io.papermc.generator.rewriter.types.registry;
import com.google.common.base.CaseFormat;
import io.papermc.generator.registry.RegistryEntries;
import io.papermc.generator.registry.RegistryEntry;
import io.papermc.generator.rewriter.types.Types;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.typewriter.ClassNamed;
import io.papermc.typewriter.replace.SearchMetadata;
import io.papermc.typewriter.replace.SearchReplaceRewriter;
import java.lang.constant.ConstantDescs;
import java.util.stream.Stream;
import net.minecraft.core.registries.Registries;
import org.bukkit.Keyed;
import org.bukkit.Registry;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class PaperRegistriesRewriter extends SearchReplaceRewriter {
private void appendEntry(String indent, StringBuilder builder, RegistryEntry<?> entry, boolean canBeDelayed, boolean apiOnly) {
builder.append(indent);
builder.append("start");
builder.append('(');
builder.append(Registries.class.getSimpleName()).append('.').append(entry.registryKeyField());
builder.append(", ");
builder.append(RegistryKey.class.getSimpleName()).append('.').append(entry.registryKeyField());
builder.append(").");
if (apiOnly) {
builder.append("apiOnly(");
if (entry.apiClass().isEnum()) {
builder.append(this.importCollector.getShortName(Types.PAPER_SIMPLE_REGISTRY)).append("::").append(CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, entry.registryKey().location().getPath()));
} else {
builder.append("() -> ");
builder.append(Registry.class.getCanonicalName()).append('.').append(entry.apiRegistryField().orElse(entry.registryKeyField()));
}
builder.append(')');
} else {
builder.append("craft(");
builder.append(this.importCollector.getShortName(entry.preloadClass())).append(".class");
builder.append(", ");
builder.append(this.importCollector.getShortName(this.getImplClassName(entry))).append("::").append(entry.apiAccessName().equals(ConstantDescs.INIT_NAME) ? "new" : entry.apiAccessName());
if (entry.canAllowDirect()) {
builder.append(", ");
builder.append(Boolean.TRUE.toString());
}
builder.append(')');
if (entry.fieldRename() != null) {
builder.append(".serializationUpdater(").append(Types.FIELD_RENAME.simpleName()).append('.').append(entry.fieldRename()).append(")");
}
if (entry.apiRegistryBuilderImpl() != null) {
builder.append(".writable(");
builder.append(this.importCollector.getShortName(this.classNamedView.findFirst(entry.apiRegistryBuilderImpl()).resolve(this.classResolver))).append("::new");
builder.append(')');
} else {
builder.append(".build()");
}
}
if (canBeDelayed && entry.isDelayed()) {
builder.append(".delayed()");
}
builder.append(',');
builder.append('\n');
}
@Override
public void insert(SearchMetadata metadata, StringBuilder builder) {
builder.append(metadata.indent()).append("// built-in");
builder.append('\n');
for (RegistryEntry<?> entry : RegistryEntries.BUILT_IN) {
appendEntry(metadata.indent(), builder, entry, false, false);
}
builder.append('\n');
builder.append(metadata.indent()).append("// data-driven");
builder.append('\n');
for (RegistryEntry<?> entry : RegistryEntries.DATA_DRIVEN) {
appendEntry(metadata.indent(), builder, entry, true, false);
}
builder.append('\n');
builder.append(metadata.indent()).append("// api-only");
builder.append('\n');
for (RegistryEntry<?> entry : RegistryEntries.API_ONLY) {
appendEntry(metadata.indent(), builder, entry, false, true);
}
builder.deleteCharAt(builder.length() - 2); // delete extra comma...
}
private ClassNamed getImplClassName(RegistryEntry<?> entry) {
try (Stream<ClassNamed> stream = this.classNamedView.find(entry.implClass())) {
return stream.map(klass -> klass.resolve(this.classResolver))
.filter(klass -> Keyed.class.isAssignableFrom(klass.knownClass())) // todo check handleable/holderable once keyed is gone
.findFirst().orElseThrow();
}
}
}

View File

@ -0,0 +1,33 @@
package io.papermc.generator.rewriter.types.registry;
import io.papermc.generator.registry.RegistryEntries;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.event.RegistryEventProvider;
import io.papermc.typewriter.replace.SearchMetadata;
import io.papermc.typewriter.replace.SearchReplaceRewriter;
import org.jspecify.annotations.NullMarked;
import static javax.lang.model.element.Modifier.FINAL;
import static javax.lang.model.element.Modifier.PUBLIC;
import static javax.lang.model.element.Modifier.STATIC;
@NullMarked
public class RegistryEventsRewriter extends SearchReplaceRewriter {
@Override
public void insert(SearchMetadata metadata, StringBuilder builder) {
RegistryEntries.forEach(entry -> {
if (entry.apiRegistryBuilder() != null) {
builder.append(metadata.indent());
builder.append("%s %s %s ".formatted(PUBLIC, STATIC, FINAL));
builder.append(RegistryEventProvider.class.getSimpleName());
builder.append("<").append(this.importCollector.getShortName(entry.apiClass())).append(", ").append(this.importCollector.getShortName(entry.apiRegistryBuilder())).append('>');
builder.append(' ');
builder.append(entry.registryKeyField());
builder.append(" = ");
builder.append("create(").append(RegistryKey.class.getSimpleName()).append('.').append(entry.registryKeyField()).append(");");
builder.append('\n');
}
});
}
}

View File

@ -0,0 +1,135 @@
package io.papermc.generator.rewriter.types.registry;
import com.google.common.base.Preconditions;
import com.google.common.base.Suppliers;
import com.mojang.logging.LogUtils;
import io.papermc.generator.Main;
import io.papermc.generator.rewriter.utils.Annotations;
import io.papermc.generator.utils.Formatting;
import io.papermc.generator.utils.experimental.ExperimentalCollector;
import io.papermc.generator.utils.experimental.SingleFlagHolder;
import io.papermc.typewriter.ClassNamed;
import io.papermc.typewriter.SourceFile;
import io.papermc.typewriter.replace.SearchMetadata;
import io.papermc.typewriter.replace.SearchReplaceRewriter;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.flag.FeatureElement;
import net.minecraft.world.flag.FeatureFlags;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import static io.papermc.generator.utils.Formatting.quoted;
import static javax.lang.model.element.Modifier.FINAL;
import static javax.lang.model.element.Modifier.PUBLIC;
import static javax.lang.model.element.Modifier.STATIC;
@NullMarked
public class RegistryFieldRewriter<T> extends SearchReplaceRewriter {
private static final Logger LOGGER = LogUtils.getLogger();
private final ResourceKey<? extends Registry<T>> registryKey;
private final boolean isFilteredRegistry;
private final @Nullable String fetchMethod;
protected @MonotonicNonNull ClassNamed fieldClass;
private @MonotonicNonNull Supplier<Map<ResourceKey<T>, SingleFlagHolder>> experimentalKeys;
public RegistryFieldRewriter(ResourceKey<? extends Registry<T>> registryKey, @Nullable String fetchMethod) {
this.registryKey = registryKey;
this.isFilteredRegistry = FeatureElement.FILTERED_REGISTRIES.contains(registryKey);
this.fetchMethod = fetchMethod;
}
@Override
public boolean registerFor(SourceFile file) {
this.fieldClass = this.options.targetClass().orElse(file.mainClass());
Preconditions.checkState(this.fieldClass.knownClass() != null, "This rewriter can't run without knowing the field class at runtime!");
if (this.fetchMethod != null) {
try {
this.fieldClass.knownClass().getDeclaredMethod(this.fetchMethod, String.class);
} catch (NoSuchMethodException e) {
LOGGER.error("Fetch method not found, skipping the rewriter for registry fields of {}", this.registryKey, e);
return false;
}
}
return super.registerFor(file);
}
@Override
protected void insert(SearchMetadata metadata, StringBuilder builder) {
boolean isInterface = Objects.requireNonNull(this.fieldClass.knownClass()).isInterface();
Registry<T> registry = Main.REGISTRY_ACCESS.lookupOrThrow(this.registryKey);
this.experimentalKeys = Suppliers.memoize(() -> ExperimentalCollector.collectDataDrivenElementIds(registry));
Iterator<Holder.Reference<T>> referenceIterator = registry.listElements().filter(this::canPrintField).sorted(Formatting.alphabeticKeyOrder(reference -> reference.key().location().getPath())).iterator();
while (referenceIterator.hasNext()) {
Holder.Reference<T> reference = referenceIterator.next();
this.rewriteJavadocs(reference, metadata.replacedContent(), metadata.indent(), builder);
SingleFlagHolder requiredFeature = this.getRequiredFeature(reference);
if (requiredFeature != null) {
Annotations.experimentalAnnotations(builder, metadata.indent(), this.importCollector, requiredFeature);
}
builder.append(metadata.indent());
if (!isInterface) {
builder.append("%s %s %s ".formatted(PUBLIC, STATIC, FINAL));
}
builder.append(this.rewriteFieldType(reference)).append(' ').append(this.rewriteFieldName(reference));
builder.append(" = ");
builder.append(this.rewriteFieldValue(reference));
builder.append(';');
builder.append('\n');
if (referenceIterator.hasNext()) {
builder.append('\n');
}
}
}
protected void rewriteJavadocs(Holder.Reference<T> reference, String replacedContent, String indent, StringBuilder builder) {
}
protected boolean canPrintField(Holder.Reference<T> reference) {
return true;
}
protected String rewriteFieldType(Holder.Reference<T> reference) {
return this.fieldClass.simpleName();
}
protected String rewriteFieldName(Holder.Reference<T> reference) {
return Formatting.formatKeyAsField(reference.key().location().getPath());
}
protected String rewriteFieldValue(Holder.Reference<T> reference) {
return "%s(%s)".formatted(this.fetchMethod, quoted(reference.key().location().getPath()));
}
protected @Nullable SingleFlagHolder getRequiredFeature(Holder.Reference<T> reference) {
if (this.isFilteredRegistry) {
// built-in registry
FeatureElement element = (FeatureElement) reference.value();
if (FeatureFlags.isExperimental(element.requiredFeatures())) {
return SingleFlagHolder.fromSet(element.requiredFeatures());
}
} else {
// data-driven registry
return this.experimentalKeys.get().get(reference.key());
}
return null;
}
}

View File

@ -0,0 +1,92 @@
package io.papermc.generator.rewriter.types.registry;
import com.google.common.base.Preconditions;
import com.mojang.logging.LogUtils;
import io.papermc.generator.Main;
import io.papermc.generator.rewriter.utils.Annotations;
import io.papermc.generator.utils.Formatting;
import io.papermc.generator.utils.experimental.SingleFlagHolder;
import io.papermc.typewriter.ClassNamed;
import io.papermc.typewriter.SourceFile;
import io.papermc.typewriter.replace.SearchMetadata;
import io.papermc.typewriter.replace.SearchReplaceRewriter;
import java.util.Iterator;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceKey;
import net.minecraft.tags.TagKey;
import org.bukkit.Keyed;
import org.bukkit.Tag;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
import org.slf4j.Logger;
import static io.papermc.generator.utils.Formatting.quoted;
import static javax.lang.model.element.Modifier.FINAL;
import static javax.lang.model.element.Modifier.PUBLIC;
import static javax.lang.model.element.Modifier.STATIC;
@NullMarked
@ApiStatus.Obsolete
public class RegistryTagRewriter<T> extends SearchReplaceRewriter {
private static final Logger LOGGER = LogUtils.getLogger();
private final ResourceKey<? extends Registry<T>> registryKey;
private final Class<? extends Keyed> apiClass;
private final String fetchMethod = "getTag";
public RegistryTagRewriter(ResourceKey<? extends Registry<T>> registryKey, Class<? extends Keyed> apiClass) {
this.registryKey = registryKey;
this.apiClass = apiClass;
}
@Override
public boolean registerFor(SourceFile file) {
ClassNamed holderClass = this.options.targetClass().orElse(file.mainClass());
Preconditions.checkState(holderClass.knownClass() != null, "This rewriter can't run without knowing the field class at runtime!");
try {
holderClass.knownClass().getDeclaredMethod(this.fetchMethod, String.class);
} catch (NoSuchMethodException e) {
LOGGER.error("Fetch method not found, skipping the rewriter for registry tag fields of {}", this.registryKey, e);
return false;
}
return super.registerFor(file);
}
@Override
protected void insert(SearchMetadata metadata, StringBuilder builder) {
Registry<T> registry = Main.REGISTRY_ACCESS.lookupOrThrow(this.registryKey);
Iterator<? extends TagKey<T>> keyIterator = registry.listTagIds().sorted(Formatting.alphabeticKeyOrder(reference -> reference.location().getPath())).iterator();
while (keyIterator.hasNext()) {
TagKey<T> tagKey = keyIterator.next();
String featureFlagName = Main.EXPERIMENTAL_TAGS.get(tagKey);
if (featureFlagName != null) {
Annotations.experimentalAnnotations(builder, metadata.indent(), this.importCollector, SingleFlagHolder.fromName(featureFlagName));
}
builder.append(metadata.indent());
builder.append("%s %s %s ".formatted(PUBLIC, STATIC, FINAL));
builder.append("%s<%s>".formatted(Tag.class.getSimpleName(), this.apiClass.getSimpleName())).append(' ').append(this.rewriteFieldName(tagKey));
builder.append(" = ");
builder.append(this.rewriteFieldValue(tagKey));
builder.append(';');
builder.append('\n');
if (keyIterator.hasNext()) {
builder.append('\n');
}
}
}
protected String rewriteFieldName(TagKey<T> tagKey) {
return Formatting.formatKeyAsField(tagKey.location().getPath());
}
protected String rewriteFieldValue(TagKey<T> tagKey) {
return "%s(%s)".formatted(this.fetchMethod, quoted(tagKey.location().getPath()));
}
}

View File

@ -0,0 +1,91 @@
package io.papermc.generator.rewriter.types.registry;
import io.papermc.generator.Main;
import io.papermc.generator.rewriter.utils.Annotations;
import io.papermc.generator.utils.Formatting;
import io.papermc.generator.utils.experimental.SingleFlagHolder;
import io.papermc.typewriter.replace.SearchMetadata;
import io.papermc.typewriter.replace.SearchReplaceRewriter;
import java.util.Iterator;
import java.util.Locale;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.tags.TagKey;
import org.bukkit.Bukkit;
import org.bukkit.Fluid;
import org.bukkit.GameEvent;
import org.bukkit.Keyed;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.entity.EntityType;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
import static io.papermc.generator.utils.Formatting.quoted;
@NullMarked
@ApiStatus.Obsolete
public class TagRewriter extends SearchReplaceRewriter {
public record TagRegistry(String legacyFolderName, Class<? extends Keyed> apiType, ResourceKey<? extends Registry<?>> registryKey) { // TODO remove Keyed
}
private static final TagRegistry[] SUPPORTED_REGISTRIES = { // 1.21 folder name are normalized to registry key but api will stay as is
new TagRegistry("blocks", Material.class, Registries.BLOCK),
new TagRegistry("items", Material.class, Registries.ITEM),
new TagRegistry("fluids", Fluid.class, Registries.FLUID),
new TagRegistry("entity_types", EntityType.class, Registries.ENTITY_TYPE),
new TagRegistry("game_events", GameEvent.class, Registries.GAME_EVENT)
// new TagRegistry("damage_types", DamageType.class, Registries.DAMAGE_TYPE) - separate in DamageTypeTags
};
@Override
protected void insert(SearchMetadata metadata, StringBuilder builder) {
for (int i = 0, len = SUPPORTED_REGISTRIES.length; i < len; i++) {
final TagRegistry tagRegistry = SUPPORTED_REGISTRIES[i];
final ResourceKey<? extends Registry<?>> registryKey = tagRegistry.registryKey();
final Registry<?> registry = Main.REGISTRY_ACCESS.lookupOrThrow(registryKey);
final String fieldPrefix = Formatting.formatTagFieldPrefix(tagRegistry.legacyFolderName(), registryKey);
final String registryFieldName = "REGISTRY_" + tagRegistry.legacyFolderName().toUpperCase(Locale.ENGLISH);
if (i != 0) {
builder.append('\n'); // extra line before the registry field
}
// registry name field
builder.append(metadata.indent());
builder.append("%s %s = %s;".formatted(String.class.getSimpleName(), registryFieldName, quoted(tagRegistry.legacyFolderName())));
builder.append('\n');
builder.append('\n');
Iterator<? extends TagKey<?>> keyIterator = registry.listTagIds().sorted(Formatting.alphabeticKeyOrder(tagKey -> tagKey.location().getPath())).iterator();
while (keyIterator.hasNext()) {
TagKey<?> tagKey = keyIterator.next();
final String keyPath = tagKey.location().getPath();
final String fieldName = fieldPrefix + Formatting.formatKeyAsField(keyPath);
// tag field
String featureFlagName = Main.EXPERIMENTAL_TAGS.get(tagKey);
if (featureFlagName != null) {
Annotations.experimentalAnnotations(builder, metadata.indent(), this.importCollector, SingleFlagHolder.fromName(featureFlagName));
}
builder.append(metadata.indent());
builder.append("%s<%s>".formatted(this.source.mainClass().simpleName(), this.importCollector.getShortName(tagRegistry.apiType()))).append(' ').append(fieldName);
builder.append(" = ");
builder.append("%s.getTag(%s, %s.minecraft(%s), %s.class)".formatted(Bukkit.class.getSimpleName(), registryFieldName, NamespacedKey.class.getSimpleName(), quoted(keyPath), tagRegistry.apiType().getSimpleName())); // assume type is imported properly
builder.append(';');
builder.append('\n');
if (keyIterator.hasNext()) {
builder.append('\n');
}
}
}
}
}

View File

@ -0,0 +1,27 @@
package io.papermc.generator.rewriter.types.simple;
import io.papermc.generator.rewriter.types.registry.RegistryFieldRewriter;
import io.papermc.generator.utils.BlockStateMapping;
import io.papermc.typewriter.util.ClassHelper;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.Registries;
import net.minecraft.world.level.block.Block;
import org.bukkit.block.BlockType;
import org.bukkit.block.data.BlockData;
public class BlockTypeRewriter extends RegistryFieldRewriter<Block> {
public BlockTypeRewriter() {
super(Registries.BLOCK, "getBlockType");
}
@Override
protected String rewriteFieldType(Holder.Reference<Block> reference) {
Class<? extends BlockData> blockData = BlockStateMapping.getBestSuitedApiClass(reference.value().getClass());
if (blockData == null) {
blockData = BlockData.class;
}
return "%s<%s>".formatted(ClassHelper.retrieveFullNestedName(BlockType.Typed.class), this.importCollector.getShortName(blockData));
}
}

View File

@ -0,0 +1,19 @@
package io.papermc.generator.rewriter.types.simple;
import io.papermc.generator.rewriter.types.Types;
import io.papermc.generator.utils.BlockStateMapping;
import io.papermc.typewriter.replace.SearchMetadata;
import io.papermc.typewriter.replace.SearchReplaceRewriter;
import java.util.Comparator;
public class CraftBlockDataMapping extends SearchReplaceRewriter {
@Override
protected void insert(SearchMetadata metadata, StringBuilder builder) {
BlockStateMapping.MAPPING.entrySet().stream().sorted(Comparator.comparing(entry -> entry.getKey().getCanonicalName())).forEach(entry -> {
builder.append(metadata.indent());
builder.append("register(%s.class, %s.block.impl.%s::new);".formatted(entry.getKey().getCanonicalName(), Types.BASE_PACKAGE, entry.getValue().implName()));
builder.append('\n');
});
}
}

View File

@ -0,0 +1,21 @@
package io.papermc.generator.rewriter.types.simple;
import io.papermc.generator.utils.BlockEntityMapping;
import io.papermc.generator.utils.Formatting;
import io.papermc.typewriter.replace.SearchMetadata;
import io.papermc.typewriter.replace.SearchReplaceRewriter;
import net.minecraft.world.level.block.entity.BlockEntityType;
public class CraftBlockEntityStateMapping extends SearchReplaceRewriter {
@Override
protected void insert(SearchMetadata metadata, StringBuilder builder) {
BlockEntityMapping.MAPPING.entrySet().stream().sorted(Formatting.alphabeticKeyOrder(entry -> entry.getKey().location().getPath())).forEach(entry -> {
builder.append(metadata.indent());
builder.append("register(%s.%s, %s.class, %s::new);".formatted(
BlockEntityType.class.getSimpleName(), Formatting.formatKeyAsField(entry.getKey().location().getPath()),
entry.getValue(), entry.getValue()));
builder.append('\n');
});
}
}

View File

@ -0,0 +1,32 @@
package io.papermc.generator.rewriter.types.simple;
import io.papermc.generator.utils.Formatting;
import io.papermc.typewriter.replace.SearchMetadata;
import io.papermc.typewriter.replace.SearchReplaceRewriter;
import java.util.Locale;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import org.bukkit.potion.PotionType;
@Deprecated(forRemoval = true)
public class CraftPotionUtilRewriter extends SearchReplaceRewriter {
private final String statePrefix;
public CraftPotionUtilRewriter(String statePrefix) {
this.statePrefix = statePrefix;
}
@Override
protected void insert(SearchMetadata metadata, StringBuilder builder) {
String upperStatePrefix = this.statePrefix.toUpperCase(Locale.ENGLISH);
BuiltInRegistries.POTION.keySet().stream()
.filter(key -> BuiltInRegistries.POTION.containsKey(key.withPath(path -> this.statePrefix + "_" + path)))
.sorted(Formatting.alphabeticKeyOrder(ResourceLocation::getPath)).forEach(key -> {
String keyedName = Formatting.formatKeyAsField(key.getPath());
builder.append(metadata.indent());
builder.append(".put(%s.%s, %s.%s_%s)".formatted(PotionType.class.getSimpleName(), keyedName, PotionType.class.getSimpleName(), upperStatePrefix, keyedName));
builder.append('\n');
});
}
}

View File

@ -0,0 +1,175 @@
package io.papermc.generator.rewriter.types.simple;
import com.google.common.base.CaseFormat;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import io.papermc.generator.registry.RegistryEntries;
import io.papermc.generator.rewriter.types.registry.EnumRegistryRewriter;
import io.papermc.generator.types.goal.MobGoalNames;
import io.papermc.typewriter.ClassNamed;
import io.papermc.typewriter.preset.model.EnumValue;
import io.papermc.typewriter.util.ClassResolver;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import net.minecraft.Util;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.Mob;
import static io.papermc.generator.utils.Formatting.quoted;
public class EntityTypeRewriter extends EnumRegistryRewriter<EntityType<?>> {
private static final Map<ResourceKey<EntityType<?>>, Class<? extends Entity>> ENTITY_GENERIC_TYPES =
RegistryEntries.byRegistryKey(Registries.ENTITY_TYPE).getFields(field -> {
if (field.getGenericType() instanceof ParameterizedType complexType && complexType.getActualTypeArguments().length == 1) {
return (Class<? extends Entity>) complexType.getActualTypeArguments()[0];
}
return null;
});
private static final Map<String, String> CLASS_RENAMES = ImmutableMap.<String, String>builder()
.put("ExperienceBottle", "ThrownExpBottle")
.put("EyeOfEnder", "EnderSignal")
.put("EndCrystal", "EnderCrystal")
.put("FireworkRocket", "Firework")
.put("FishingBobber", "FishHook")
.put("LeashKnot", "LeashHitch")
.put("LightningBolt", "LightningStrike")
.put("Tnt", "TNTPrimed")
.put("ChestMinecart", "StorageMinecart")
.put("CommandBlockMinecart", "CommandMinecart")
.put("TntMinecart", "ExplosiveMinecart")
.put("FurnaceMinecart", "PoweredMinecart")
.buildOrThrow();
@Deprecated
private static final Object2IntMap<EntityType<?>> LEGACY_ID = Util.make(new Object2IntOpenHashMap<>(), map -> {
map.put(EntityType.ITEM, 1);
map.put(EntityType.EXPERIENCE_ORB, 2);
map.put(EntityType.AREA_EFFECT_CLOUD, 3);
map.put(EntityType.ELDER_GUARDIAN, 4);
map.put(EntityType.WITHER_SKELETON, 5);
map.put(EntityType.STRAY, 6);
map.put(EntityType.EGG, 7);
map.put(EntityType.LEASH_KNOT, 8);
map.put(EntityType.PAINTING, 9);
map.put(EntityType.ARROW, 10);
map.put(EntityType.SNOWBALL, 11);
map.put(EntityType.FIREBALL, 12);
map.put(EntityType.SMALL_FIREBALL, 13);
map.put(EntityType.ENDER_PEARL, 14);
map.put(EntityType.EYE_OF_ENDER, 15);
map.put(EntityType.SPLASH_POTION, 16);
map.put(EntityType.EXPERIENCE_BOTTLE, 17);
map.put(EntityType.ITEM_FRAME, 18);
map.put(EntityType.WITHER_SKULL, 19);
map.put(EntityType.TNT, 20);
map.put(EntityType.FALLING_BLOCK, 21);
map.put(EntityType.FIREWORK_ROCKET, 22);
map.put(EntityType.HUSK, 23);
map.put(EntityType.SPECTRAL_ARROW, 24);
map.put(EntityType.SHULKER_BULLET, 25);
map.put(EntityType.DRAGON_FIREBALL, 26);
map.put(EntityType.ZOMBIE_VILLAGER, 27);
map.put(EntityType.SKELETON_HORSE, 28);
map.put(EntityType.ZOMBIE_HORSE, 29);
map.put(EntityType.ARMOR_STAND, 30);
map.put(EntityType.DONKEY, 31);
map.put(EntityType.MULE, 32);
map.put(EntityType.EVOKER_FANGS, 33);
map.put(EntityType.EVOKER, 34);
map.put(EntityType.VEX, 35);
map.put(EntityType.VINDICATOR, 36);
map.put(EntityType.ILLUSIONER, 37);
map.put(EntityType.COMMAND_BLOCK_MINECART, 40);
map.put(EntityType.MINECART, 42);
map.put(EntityType.CHEST_MINECART, 43);
map.put(EntityType.FURNACE_MINECART, 44);
map.put(EntityType.TNT_MINECART, 45);
map.put(EntityType.HOPPER_MINECART, 46);
map.put(EntityType.SPAWNER_MINECART, 47);
map.put(EntityType.CREEPER, 50);
map.put(EntityType.SKELETON, 51);
map.put(EntityType.SPIDER, 52);
map.put(EntityType.GIANT, 53);
map.put(EntityType.ZOMBIE, 54);
map.put(EntityType.SLIME, 55);
map.put(EntityType.GHAST, 56);
map.put(EntityType.ZOMBIFIED_PIGLIN, 57);
map.put(EntityType.ENDERMAN, 58);
map.put(EntityType.CAVE_SPIDER, 59);
map.put(EntityType.SILVERFISH, 60);
map.put(EntityType.BLAZE, 61);
map.put(EntityType.MAGMA_CUBE, 62);
map.put(EntityType.ENDER_DRAGON, 63);
map.put(EntityType.WITHER, 64);
map.put(EntityType.BAT, 65);
map.put(EntityType.WITCH, 66);
map.put(EntityType.ENDERMITE, 67);
map.put(EntityType.GUARDIAN, 68);
map.put(EntityType.SHULKER, 69);
map.put(EntityType.PIG, 90);
map.put(EntityType.SHEEP, 91);
map.put(EntityType.COW, 92);
map.put(EntityType.CHICKEN, 93);
map.put(EntityType.SQUID, 94);
map.put(EntityType.WOLF, 95);
map.put(EntityType.MOOSHROOM, 96);
map.put(EntityType.SNOW_GOLEM, 97);
map.put(EntityType.OCELOT, 98);
map.put(EntityType.IRON_GOLEM, 99);
map.put(EntityType.HORSE, 100);
map.put(EntityType.RABBIT, 101);
map.put(EntityType.POLAR_BEAR, 102);
map.put(EntityType.LLAMA, 103);
map.put(EntityType.LLAMA_SPIT, 104);
map.put(EntityType.PARROT, 105);
map.put(EntityType.VILLAGER, 120);
map.put(EntityType.END_CRYSTAL, 200);
});
private static final ClassResolver runtime = new ClassResolver(EntityTypeRewriter.class.getClassLoader());
public EntityTypeRewriter() {
super(Registries.ENTITY_TYPE, false);
}
@Override
protected EnumValue.Builder rewriteEnumValue(Holder.Reference<EntityType<?>> reference) {
String path = reference.key().location().getPath();
List<String> arguments = new ArrayList<>(4);
arguments.add(quoted(path));
arguments.add(toBukkitClass(reference).concat(".class"));
arguments.add(Integer.toString(LEGACY_ID.getOrDefault(reference.value(), -1)));
if (!reference.value().canSummon()) {
arguments.add(Boolean.FALSE.toString());
}
return super.rewriteEnumValue(reference).arguments(arguments);
}
private String toBukkitClass(Holder.Reference<EntityType<?>> reference) {
Class<? extends Entity> internalClass = ENTITY_GENERIC_TYPES.get(reference.key());
if (Mob.class.isAssignableFrom(internalClass)) {
return this.importCollector.getShortName(MobGoalNames.bukkitMap.get((Class<? extends Mob>) internalClass));
}
String className = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, reference.key().location().getPath()); // use the key instead of the internal class name since name match a bit more
ClassNamed resolvedClass = this.classNamedView.findFirst(CLASS_RENAMES.getOrDefault(className, className)).resolve(runtime);
Preconditions.checkArgument(org.bukkit.entity.Entity.class.isAssignableFrom(resolvedClass.knownClass()), "Generic type must be an entity");
return this.importCollector.getShortName(this.classNamedView.findFirst(CLASS_RENAMES.getOrDefault(className, className)).resolve(runtime));
}
}

View File

@ -0,0 +1,27 @@
package io.papermc.generator.rewriter.types.simple;
import io.papermc.generator.rewriter.types.registry.RegistryFieldRewriter;
import io.papermc.typewriter.util.ClassHelper;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.Registries;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.Items;
import org.bukkit.inventory.ItemType;
import org.bukkit.inventory.meta.ItemMeta;
@Deprecated // bad generic
public class ItemTypeRewriter extends RegistryFieldRewriter<Item> {
public ItemTypeRewriter() {
super(Registries.ITEM, "getItemType");
}
@Override
protected String rewriteFieldType(Holder.Reference<Item> reference) {
if (reference.value().equals(Items.AIR)) {
return super.rewriteFieldType(reference);
}
return "%s<%s>".formatted(ClassHelper.retrieveFullNestedName(ItemType.Typed.class), ItemMeta.class.getSimpleName());
}
}

View File

@ -0,0 +1,41 @@
package io.papermc.generator.rewriter.types.simple;
import com.mojang.logging.LogUtils;
import io.papermc.typewriter.replace.SearchMetadata;
import io.papermc.typewriter.replace.SearchReplaceRewriter;
import java.awt.Color;
import net.minecraft.world.level.material.MapColor;
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
public class MapPaletteRewriter extends SearchReplaceRewriter {
private static final Logger LOGGER = LogUtils.getLogger();
private static final boolean UPDATING = Boolean.getBoolean("paper.updatingMinecraft");
@Override
protected void insert(SearchMetadata metadata, StringBuilder builder) {
int count = 0;
for (@Nullable MapColor mapColor : MapColor.MATERIAL_COLORS) {
if (mapColor == null) {
continue;
}
for (MapColor.Brightness brightness : MapColor.Brightness.values()) {
builder.append(metadata.indent());
Color color = new Color(mapColor.calculateARGBColor(brightness), true);
if (color.getAlpha() != 0xFF) {
builder.append("new %s(0x%08X, true),".formatted(color.getClass().getSimpleName(), color.getRGB()));
} else {
builder.append("new %s(0x%06X),".formatted(color.getClass().getSimpleName(), color.getRGB() & 0x00FFFFFF));
}
builder.append('\n');
count++;
}
}
if (UPDATING) {
LOGGER.warn("There are {} map colors, check CraftMapView#render for possible change and update md5 hash in CraftMapColorCache", count);
}
}
}

View File

@ -0,0 +1,104 @@
package io.papermc.generator.rewriter.types.simple;
import io.papermc.generator.rewriter.types.registry.EnumRegistryRewriter;
import io.papermc.generator.utils.BlockStateMapping;
import io.papermc.generator.utils.Formatting;
import io.papermc.typewriter.preset.model.EnumValue;
import java.util.Optional;
import net.minecraft.core.Holder;
import net.minecraft.core.component.DataComponents;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.WallSignBlock;
import org.bukkit.block.data.BlockData;
import static io.papermc.generator.utils.Formatting.asCode;
@Deprecated(forRemoval = true)
public class MaterialRewriter {
// blocks
public static class Blocks extends EnumRegistryRewriter<Block> {
public Blocks() {
super(Registries.BLOCK, false);
}
@Override
protected Iterable<Holder.Reference<Block>> getValues() {
return BuiltInRegistries.BLOCK.listElements().filter(reference -> !reference.value().equals(net.minecraft.world.level.block.Blocks.AIR))
.sorted(Formatting.alphabeticKeyOrder(reference -> reference.key().location().getPath()))::iterator;
}
@Override
protected EnumValue.Builder rewriteEnumValue(Holder.Reference<Block> reference) {
EnumValue.Builder value = super.rewriteEnumValue(reference);
Block block = reference.value();
if (BlockStateMapping.MAPPING.containsKey(block.getClass())) {
// some block can also be represented as item in that enum
// doing a double job
Optional<Item> equivalentItem = BuiltInRegistries.ITEM.getOptional(reference.key().location());
if (equivalentItem.isEmpty() && block instanceof WallSignBlock) {
// wall sign block stack size is 16 for some reason like the sign item?
// but that rule doesn't work for the wall hanging sign block??
equivalentItem = Optional.of(block.asItem());
}
Class<?> blockData = BlockStateMapping.getBestSuitedApiClass(block.getClass());
if (blockData == null) {
blockData = BlockData.class;
}
if (equivalentItem.isPresent() && equivalentItem.get().getDefaultMaxStackSize() != Item.DEFAULT_MAX_STACK_SIZE) {
return value.arguments(Integer.toString(-1), Integer.toString(equivalentItem.get().getDefaultMaxStackSize()), this.importCollector.getShortName(blockData).concat(".class"));
}
return value.arguments(Integer.toString(-1), this.importCollector.getShortName(blockData).concat(".class"));
}
return value.argument(Integer.toString(-1)); // id not needed for non legacy material
}
}
/* todo test is broken
public static class IsTransparent extends SwitchCaseRewriter {
public IsTransparent() {
super(false);
}
@Override
protected Iterable<String> getCases() {
return BuiltInRegistries.BLOCK.holders().filter(reference -> reference.value().defaultBlockState().useShapeForLightOcclusion())
.map(reference -> reference.key().location().getPath().toUpperCase(Locale.ENGLISH)).sorted(Formatting.ALPHABETIC_KEY_ORDER)::iterator;
}
}*/
// items
public static class Items extends EnumRegistryRewriter<Item> {
public Items() {
super(Registries.ITEM, false);
}
@Override
protected Iterable<Holder.Reference<Item>> getValues() {
return BuiltInRegistries.ITEM.listElements().filter(reference -> BuiltInRegistries.BLOCK.getOptional(reference.key().location()).isEmpty() || reference.value().equals(net.minecraft.world.item.Items.AIR))
.sorted(Formatting.alphabeticKeyOrder(reference -> reference.key().location().getPath()))::iterator;
}
@Override
protected EnumValue.Builder rewriteEnumValue(Holder.Reference<Item> reference) {
EnumValue.Builder value = super.rewriteEnumValue(reference);
Item item = reference.value();
int maxStackSize = item.getDefaultMaxStackSize();
if (maxStackSize != Item.DEFAULT_MAX_STACK_SIZE) {
return value.arguments(asCode(-1, maxStackSize));
}
return value.argument(Integer.toString(-1)); // id not needed for non legacy material
}
}
}

View File

@ -0,0 +1,116 @@
package io.papermc.generator.rewriter.types.simple;
import com.google.gson.internal.Primitives;
import io.papermc.generator.registry.RegistryEntries;
import io.papermc.generator.rewriter.types.registry.RegistryFieldRewriter;
import io.papermc.generator.utils.ClassHelper;
import java.lang.reflect.ParameterizedType;
import java.util.Map;
import java.util.Set;
import net.minecraft.core.BlockPos;
import net.minecraft.core.GlobalPos;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.util.Unit;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.ai.behavior.PositionTracker;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.ai.memory.NearestVisibleLivingEntities;
import net.minecraft.world.entity.ai.memory.WalkTarget;
import net.minecraft.world.level.pathfinder.Path;
import net.minecraft.world.phys.Vec3;
import org.bukkit.Location;
import org.bukkit.NamespacedKey;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import static io.papermc.generator.utils.Formatting.quoted;
@Deprecated
public class MemoryKeyRewriter extends RegistryFieldRewriter<MemoryModuleType<?>> {
private static final Map<ResourceKey<MemoryModuleType<?>>, Class<?>> MEMORY_GENERIC_TYPES = RegistryEntries.byRegistryKey(Registries.MEMORY_MODULE_TYPE).getFields(field -> {
if (field.getGenericType() instanceof ParameterizedType complexType && complexType.getActualTypeArguments().length == 1) {
return ClassHelper.eraseType(complexType.getActualTypeArguments()[0]);
}
return null;
});
public MemoryKeyRewriter() {
super(Registries.MEMORY_MODULE_TYPE, null);
}
// this api is not implemented and is not backed by a proper registry
private static final Set<Class<?>> IGNORED_TYPES = Set.of(
NearestVisibleLivingEntities.class,
WalkTarget.class,
PositionTracker.class,
Path.class,
DamageSource.class,
Vec3.class,
BlockPos.class,
Unit.class,
Void.class
);
private static final Set<Class<?>> IGNORED_SUB_TYPES = Set.of(
Iterable.class,
Map.class,
Entity.class
);
private static final Map<Class<?>, Class<?>> API_BRIDGE = Map.of(
GlobalPos.class, Location.class
);
private static final Map<String, String> FIELD_RENAMES = Map.of(
"LIKED_NOTEBLOCK", "LIKED_NOTEBLOCK_POSITION"
);
@Override
protected boolean canPrintField(Holder.Reference<MemoryModuleType<?>> reference) {
Class<?> memoryType = MEMORY_GENERIC_TYPES.get(reference.key());
if (IGNORED_TYPES.contains(memoryType)) {
return false;
}
for (Class<?> subType : IGNORED_SUB_TYPES) {
if (subType.isAssignableFrom(memoryType)) {
return false;
}
}
return true;
}
private @MonotonicNonNull Class<?> apiMemoryType;
@Override
protected String rewriteFieldType(Holder.Reference<MemoryModuleType<?>> reference) {
Class<?> memoryType = MEMORY_GENERIC_TYPES.get(reference.key());
if (!Primitives.isWrapperType(memoryType) && API_BRIDGE.containsKey(memoryType)) {
this.apiMemoryType = API_BRIDGE.get(memoryType);
} else {
this.apiMemoryType = memoryType;
}
return "%s<%s>".formatted(this.fieldClass.simpleName(), this.importCollector.getShortName(this.apiMemoryType));
}
@Override
protected String rewriteFieldName(Holder.Reference<MemoryModuleType<?>> reference) {
String keyedName = super.rewriteFieldName(reference);
return FIELD_RENAMES.getOrDefault(keyedName, keyedName);
}
@Override
protected String rewriteFieldValue(Holder.Reference<MemoryModuleType<?>> reference) {
return "new %s<>(%s.minecraft(%s), %s.class)".formatted(
this.fieldClass.simpleName(),
NamespacedKey.class.getSimpleName(),
quoted(reference.key().location().getPath()),
this.apiMemoryType.getSimpleName() // assume the type is already import (see above in rewriteFieldType)
);
}
}

View File

@ -0,0 +1,170 @@
package io.papermc.generator.rewriter.types.simple;
import com.google.common.collect.ImmutableMap;
import io.papermc.generator.rewriter.types.registry.EnumRegistryRewriter;
import io.papermc.generator.utils.ClassHelper;
import io.papermc.generator.utils.Formatting;
import io.papermc.typewriter.preset.model.EnumValue;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Map;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.stats.StatType;
import net.minecraft.stats.Stats;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import org.bukkit.Statistic;
import static io.papermc.generator.utils.Formatting.quoted;
@Deprecated(forRemoval = true)
public class StatisticRewriter {
private static final Map<String, String> FIELD_RENAMES = ImmutableMap.<String, String>builder()
.put("DROP", "DROP_COUNT")
.put("DROPPED", "DROP")
.put("PICKED_UP", "PICKUP")
.put("PLAY_TIME", "PLAY_ONE_MINUTE")
.put("CROUCH_TIME", "SNEAK_TIME")
.put("MINED", "MINE_BLOCK")
.put("USED", "USE_ITEM")
.put("BROKEN", "BREAK_ITEM")
.put("CRAFTED", "CRAFT_ITEM")
.put("KILLED", "KILL_ENTITY")
.put("KILLED_BY", "ENTITY_KILLED_BY")
.put("EAT_CAKE_SLICE", "CAKE_SLICES_EATEN")
.put("FILL_CAULDRON", "CAULDRON_FILLED")
.put("USE_CAULDRON", "CAULDRON_USED")
.put("CLEAN_ARMOR", "ARMOR_CLEANED")
.put("CLEAN_BANNER", "BANNER_CLEANED")
.put("INTERACT_WITH_BREWINGSTAND", "BREWINGSTAND_INTERACTION")
.put("INTERACT_WITH_BEACON", "BEACON_INTERACTION")
.put("INSPECT_DROPPER", "DROPPER_INSPECTED")
.put("INSPECT_HOPPER", "HOPPER_INSPECTED")
.put("INSPECT_DISPENSER", "DISPENSER_INSPECTED")
.put("PLAY_NOTEBLOCK", "NOTEBLOCK_PLAYED")
.put("TUNE_NOTEBLOCK", "NOTEBLOCK_TUNED")
.put("POT_FLOWER", "FLOWER_POTTED")
.put("TRIGGER_TRAPPED_CHEST", "TRAPPED_CHEST_TRIGGERED")
.put("OPEN_ENDERCHEST", "ENDERCHEST_OPENED")
.put("ENCHANT_ITEM", "ITEM_ENCHANTED")
.put("PLAY_RECORD", "RECORD_PLAYED")
.put("INTERACT_WITH_FURNACE", "FURNACE_INTERACTION")
.put("INTERACT_WITH_CRAFTING_TABLE", "CRAFTING_TABLE_INTERACTION")
.put("OPEN_CHEST", "CHEST_OPENED")
.put("OPEN_SHULKER_BOX", "SHULKER_BOX_OPENED")
.buildOrThrow();
public static class Custom extends EnumRegistryRewriter<ResourceLocation> {
public Custom() {
super(Registries.CUSTOM_STAT, false);
}
@Override
protected EnumValue.Builder rewriteEnumValue(Holder.Reference<ResourceLocation> reference) {
return super.rewriteEnumValue(reference).rename(name -> FIELD_RENAMES.getOrDefault(name, name));
}
}
public static class CraftCustom extends EnumRegistryRewriter<ResourceLocation> {
private static final Map<String, String> INTERNAL_FIELD_RENAMES = Map.of(
"SNEAK_TIME", "CROUCH_TIME"
);
public CraftCustom() {
super(Registries.CUSTOM_STAT, false);
}
@Override
protected EnumValue.Builder rewriteEnumValue(Holder.Reference<ResourceLocation> reference) {
String keyedName = Formatting.formatKeyAsField(reference.key().location().getPath());
return super.rewriteEnumValue(reference)
.rename(name -> FIELD_RENAMES.getOrDefault(name, name))
.argument("%s.%s".formatted(Stats.class.getSimpleName(), INTERNAL_FIELD_RENAMES.getOrDefault(keyedName, keyedName)));
}
}
public static class Type extends EnumRegistryRewriter<StatType<?>> {
private static final Map<Class<?>, String> TYPE_MAPPING = Map.of(
Item.class, "ITEM",
Block.class, "BLOCK",
EntityType.class, "ENTITY"
);
private static final Map<StatType<?>, Class<?>> FIELD_GENERIC_TYPE;
static {
final Map<StatType<?>, Class<?>> map = new IdentityHashMap<>();
try {
for (Field field : Stats.class.getDeclaredFields()) {
if (field.getType() != StatType.class) {
continue;
}
if (ClassHelper.isStaticConstant(field, Modifier.PUBLIC)) {
java.lang.reflect.Type genericType = ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
map.put((StatType<?>) field.get(null), ClassHelper.eraseType(genericType));
}
}
} catch (ReflectiveOperationException ex) {
throw new RuntimeException(ex);
}
FIELD_GENERIC_TYPE = Collections.unmodifiableMap(map);
}
public Type() {
super(Registries.STAT_TYPE, false);
}
@Override
protected Iterable<Holder.Reference<StatType<?>>> getValues() {
return BuiltInRegistries.STAT_TYPE.listElements().filter(reference -> reference.value() != Stats.CUSTOM)
.sorted(Formatting.alphabeticKeyOrder(reference -> reference.key().location().getPath()))::iterator;
}
@Override
protected EnumValue.Builder rewriteEnumValue(Holder.Reference<StatType<?>> reference) {
Class<?> genericType = FIELD_GENERIC_TYPE.get(reference.value());
if (!TYPE_MAPPING.containsKey(genericType)) {
throw new IllegalStateException("Unable to translate stat type generic " + genericType.getCanonicalName() + " into the api!");
}
return super.rewriteEnumValue(reference)
.rename(name -> FIELD_RENAMES.getOrDefault(name, name))
.argument("%s.%s".formatted(Statistic.Type.class.getSimpleName(), TYPE_MAPPING.get(genericType))); // find a more direct way?
}
}
public static class CraftType extends EnumRegistryRewriter<StatType<?>> {
public CraftType() {
super(Registries.STAT_TYPE, false);
}
@Override
protected Iterable<Holder.Reference<StatType<?>>> getValues() {
return BuiltInRegistries.STAT_TYPE.listElements().filter(reference -> reference.value() != Stats.CUSTOM)
.sorted(Formatting.alphabeticKeyOrder(reference -> reference.key().location().getPath()))::iterator;
}
@Override
protected EnumValue.Builder rewriteEnumValue(Holder.Reference<StatType<?>> reference) {
return super.rewriteEnumValue(reference)
.rename(name -> FIELD_RENAMES.getOrDefault(name, name))
.argument("%s.withDefaultNamespace(%s)".formatted(ResourceLocation.class.getSimpleName(), quoted(reference.key().location().getPath())));
}
}
}

View File

@ -0,0 +1,4 @@
@NullMarked
package io.papermc.generator.rewriter.types.simple;
import org.jspecify.annotations.NullMarked;

View File

@ -0,0 +1,261 @@
package io.papermc.generator.rewriter.types.simple.trial;
import com.google.gson.internal.Primitives;
import com.mojang.serialization.Codec;
import io.papermc.generator.registry.RegistryEntries;
import io.papermc.generator.rewriter.types.registry.RegistryFieldRewriter;
import io.papermc.generator.utils.ClassHelper;
import io.papermc.paper.datacomponent.item.BlockItemDataProperties;
import io.papermc.paper.datacomponent.item.ItemAdventurePredicate;
import io.papermc.paper.datacomponent.item.ItemArmorTrim;
import io.papermc.typewriter.ClassNamed;
import io.papermc.typewriter.parser.Lexer;
import io.papermc.typewriter.parser.sequence.SequenceTokens;
import io.papermc.typewriter.parser.sequence.TokenTaskBuilder;
import io.papermc.typewriter.parser.token.CharSequenceBlockToken;
import io.papermc.typewriter.parser.token.CharSequenceToken;
import io.papermc.typewriter.parser.token.TokenType;
import io.papermc.typewriter.replace.SearchMetadata;
import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
import net.kyori.adventure.key.Key;
import net.minecraft.core.Holder;
import net.minecraft.core.component.DataComponentType;
import net.minecraft.core.component.DataComponents;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.ExtraCodecs;
import net.minecraft.util.Unit;
import net.minecraft.world.item.AdventureModePredicate;
import net.minecraft.world.item.Instrument;
import net.minecraft.world.item.Rarity;
import net.minecraft.world.item.component.BlockItemStateProperties;
import net.minecraft.world.item.component.FireworkExplosion;
import net.minecraft.world.item.crafting.Recipe;
import net.minecraft.world.item.equipment.trim.ArmorTrim;
import org.bukkit.FireworkEffect;
import org.bukkit.MusicInstrument;
import org.bukkit.inventory.ItemRarity;
import org.checkerframework.checker.index.qual.NonNegative;
import org.checkerframework.checker.index.qual.Positive;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.jspecify.annotations.Nullable;
import static io.papermc.generator.utils.Formatting.quoted;
public class DataComponentTypesRewriter extends RegistryFieldRewriter<DataComponentType<?>> {
public DataComponentTypesRewriter() {
super(Registries.DATA_COMPONENT_TYPE, null);
}
private static final Set<TokenType> FORMAT_TOKENS = EnumSet.of(
TokenType.COMMENT,
TokenType.SINGLE_COMMENT
);
private @MonotonicNonNull Map<String, CharSequenceBlockToken> javadocsPerConstant;
private Map<String, CharSequenceBlockToken> parseConstantJavadocs(String content) {
Map<String, CharSequenceBlockToken> map = new HashMap<>();
Lexer lex = new Lexer(content.toCharArray());
lex.checkMarkdownDocComments = !this.sourcesMetadata.canSkipMarkdownDocComments();
SequenceTokens.wrap(lex, FORMAT_TOKENS)
.group(action -> {
ProtoConstant constant = new ProtoConstant();
action
.map(TokenType.JAVADOC, token -> {
constant.javadocs(((CharSequenceBlockToken) token));
}, TokenTaskBuilder::asOptional)
.skip(TokenType.PUBLIC).skip(TokenType.STATIC).skip(TokenType.FINAL)
.skipQualifiedName(Predicate.isEqual(TokenType.JAVADOC))
.skipClosure(TokenType.LT, TokenType.GT, true, TokenTaskBuilder::asOptional) // skip generic
.map(TokenType.IDENTIFIER, token -> {
constant.name(((CharSequenceToken) token).value());
})
.skip(TokenType.IDENTIFIER)
.skipClosure(TokenType.LPAREN, TokenType.RPAREN, true)
.map(TokenType.SECO, $ -> {
if (constant.isComplete()) {
map.put(constant.name(), constant.javadocs());
}
});
}, TokenTaskBuilder::asRepeatable)
.executeOrThrow();
return map;
}
private static final Set<DataComponentType<?>> UNSUPPORTED_TYPES = Set.of(
DataComponents.CUSTOM_DATA,
DataComponents.CREATIVE_SLOT_LOCK,
DataComponents.DEBUG_STICK_STATE,
DataComponents.ENTITY_DATA,
DataComponents.BUCKET_ENTITY_DATA,
DataComponents.BLOCK_ENTITY_DATA,
DataComponents.BEES,
DataComponents.LOCK
);
private static final Map<ResourceKey<DataComponentType<?>>, Type> COMPONENT_GENERIC_TYPES = RegistryEntries.byRegistryKey(Registries.DATA_COMPONENT_TYPE).getFields(field -> {
if (field.getGenericType() instanceof ParameterizedType complexType && complexType.getActualTypeArguments().length == 1) {
return complexType.getActualTypeArguments()[0];
}
return null;
});
private static final Map<Class<?>, Class<?>> API_BRIDGE = Map.of(
Component.class, net.kyori.adventure.text.Component.class,
ResourceLocation.class, Key.class,
Instrument.class, MusicInstrument.class,
FireworkExplosion.class, FireworkEffect.class,
Rarity.class, ItemRarity.class,
ArmorTrim.class, ItemArmorTrim.class,
// renames
BlockItemStateProperties.class, BlockItemDataProperties.class,
AdventureModePredicate.class, ItemAdventurePredicate.class
);
@Deprecated
private static final Map<String, String> FIELD_RENAMES = Map.of(
"BLOCK_STATE", "BLOCK_DATA"
);
@Override
protected void insert(SearchMetadata metadata, StringBuilder builder) {
this.javadocsPerConstant = parseConstantJavadocs(metadata.replacedContent());
super.insert(metadata, builder);
}
@Override
protected boolean canPrintField(Holder.Reference<DataComponentType<?>> reference) {
return !UNSUPPORTED_TYPES.contains(reference.value());
}
@Override
protected void rewriteJavadocs(Holder.Reference<DataComponentType<?>> reference, String replacedContent, String indent, StringBuilder builder) {
String constantName = this.rewriteFieldName(reference);
if (this.javadocsPerConstant.containsKey(constantName)) {
CharSequenceBlockToken token = this.javadocsPerConstant.get(constantName);
builder.append(indent).append(replacedContent, token.pos(), token.endPos()).append('\n');
}
}
private boolean isValued;
private Class<?> handleParameterizedType(Type type) {
if (type instanceof ParameterizedType complexType) {
Type[] args = complexType.getActualTypeArguments();
if (args.length != 1) {
throw new UnsupportedOperationException("Unsupported type " + complexType);
}
Class<?> baseClass = ClassHelper.eraseType(complexType);
if (baseClass == Holder.class) {
return ClassHelper.eraseType(args[0]);
}
if (baseClass == ResourceKey.class) {
Class<?> componentClass = ClassHelper.eraseType(args[0]);
if (componentClass == Recipe.class) {
return ResourceLocation.class; // special case recipe registry is not really a thing
}
}
}
throw new UnsupportedOperationException("Unsupported type " + type);
}
@Override
protected String rewriteFieldType(Holder.Reference<DataComponentType<?>> reference) {
Type componentType = COMPONENT_GENERIC_TYPES.get(reference.key());
this.isValued = componentType != Unit.class;
if (this.isValued) {
Class<?> componentClass = null;
UnaryOperator<String> tryToWrap = UnaryOperator.identity();
if (!reference.value().isTransient()) {
final Class<? extends Annotation> annotation = getEquivalentAnnotation(reference.value().codecOrThrow());
if (annotation != null) {
tryToWrap = value -> "@%s %s".formatted(this.importCollector.getShortName(annotation), value);
}
}
if (componentType instanceof Class<?> clazz) {
componentClass = clazz;
} else if (componentType instanceof ParameterizedType complexType) {
Type[] args = complexType.getActualTypeArguments();
if (args.length != 1) {
throw new UnsupportedOperationException("Unsupported type " + componentType);
}
Class<?> baseClass = ClassHelper.eraseType(complexType);
if (baseClass == List.class) {
tryToWrap = value -> "%s<%s>".formatted(this.importCollector.getShortName(List.class), value);
componentClass = this.handleParameterizedType(args[0]);
} else {
componentClass = this.handleParameterizedType(complexType);
}
}
if (componentClass == null) {
throw new UnsupportedOperationException("Unsupported type " + componentType);
}
Class<?> apiComponentClass = null;
if (Primitives.isWrapperType(componentClass)) {
apiComponentClass = componentClass;
} else if (API_BRIDGE.containsKey(componentClass)) {
apiComponentClass = API_BRIDGE.get(componentClass);
}
final ClassNamed finalClass;
if (apiComponentClass == null) {
finalClass = this.classNamedView.tryFindFirst(io.papermc.typewriter.util.ClassHelper.retrieveFullNestedName(componentClass)).orElse(null);
} else {
finalClass = new ClassNamed(apiComponentClass);
}
return "%s.%s<%s>".formatted(
io.papermc.paper.datacomponent.DataComponentType.class.getSimpleName(),
io.papermc.paper.datacomponent.DataComponentType.Valued.class.getSimpleName(),
tryToWrap.apply(Optional.ofNullable(finalClass).map(this.importCollector::getShortName).orElse(componentClass.getSimpleName()))
);
} else {
return "%s.%s".formatted(
io.papermc.paper.datacomponent.DataComponentType.class.getSimpleName(),
io.papermc.paper.datacomponent.DataComponentType.NonValued.class.getSimpleName()
);
}
}
private @Nullable Class<? extends Annotation> getEquivalentAnnotation(Codec<?> codec) {
Class<? extends Annotation> annotation = null; // int range maybe?
if (codec == ExtraCodecs.POSITIVE_INT || codec == ExtraCodecs.POSITIVE_FLOAT) {
annotation = Positive.class;
} else if (codec == ExtraCodecs.NON_NEGATIVE_INT || codec == ExtraCodecs.NON_NEGATIVE_FLOAT) {
annotation = NonNegative.class;
}
return annotation;
}
@Override
protected String rewriteFieldName(Holder.Reference<DataComponentType<?>> reference) {
String keyedName = super.rewriteFieldName(reference);
return FIELD_RENAMES.getOrDefault(keyedName, keyedName);
}
@Override
protected String rewriteFieldValue(Holder.Reference<DataComponentType<?>> reference) {
return "%s(%s)".formatted(this.isValued ? "valued" : "unvalued", quoted(reference.key().location().getPath()));
}
}

View File

@ -0,0 +1,30 @@
package io.papermc.generator.rewriter.types.simple.trial;
import io.papermc.typewriter.parser.token.CharSequenceBlockToken;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
class ProtoConstant {
private @MonotonicNonNull String name;
private @MonotonicNonNull CharSequenceBlockToken token;
public void name(String name) {
this.name = name;
}
public void javadocs(CharSequenceBlockToken token) {
this.token = token;
}
public String name() {
return this.name;
}
public CharSequenceBlockToken javadocs() {
return this.token;
}
public boolean isComplete() {
return this.name != null && this.token != null;
}
}

View File

@ -0,0 +1,101 @@
package io.papermc.generator.rewriter.types.simple.trial;
import io.papermc.generator.rewriter.types.registry.RegistryFieldRewriter;
import io.papermc.typewriter.parser.Lexer;
import io.papermc.typewriter.parser.sequence.SequenceTokens;
import io.papermc.typewriter.parser.sequence.TokenTaskBuilder;
import io.papermc.typewriter.parser.token.CharSequenceBlockToken;
import io.papermc.typewriter.parser.token.CharSequenceToken;
import io.papermc.typewriter.parser.token.TokenType;
import io.papermc.typewriter.replace.SearchMetadata;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.Registries;
import net.minecraft.world.entity.npc.VillagerProfession;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
public class VillagerProfessionRewriter extends RegistryFieldRewriter<VillagerProfession> {
public VillagerProfessionRewriter() {
super(Registries.VILLAGER_PROFESSION, "getProfession");
}
private static final Set<TokenType> FORMAT_TOKENS = EnumSet.of(
TokenType.COMMENT,
TokenType.SINGLE_COMMENT
);
private @MonotonicNonNull Map<String, CharSequenceBlockToken> javadocsPerConstant;
private Map<String, CharSequenceBlockToken> parseConstantJavadocs(String content) {
Map<String, CharSequenceBlockToken> map = new HashMap<>();
Lexer lex = new Lexer(content.toCharArray());
lex.checkMarkdownDocComments = !this.sourcesMetadata.canSkipMarkdownDocComments();
SequenceTokens.wrap(lex, FORMAT_TOKENS)
.group(action -> {
ProtoConstant constant = new ProtoConstant();
action
.map(TokenType.JAVADOC, token -> {
constant.javadocs(((CharSequenceBlockToken) token));
}, TokenTaskBuilder::asOptional)
.skipQualifiedName(Predicate.isEqual(TokenType.JAVADOC))
.map(TokenType.IDENTIFIER, token -> {
constant.name(((CharSequenceToken) token).value());
})
.skip(TokenType.IDENTIFIER)
.skipClosure(TokenType.LPAREN, TokenType.RPAREN, true)
.map(TokenType.SECO, $ -> {
if (constant.isComplete()) {
map.put(constant.name(), constant.javadocs());
}
});
}, TokenTaskBuilder::asRepeatable)
.executeOrThrow();
/*
for enums:
Set<TokenType> endMarkers = Set.of(TokenType.CO, TokenType.SECO); // move to static
SequenceTokens.wrap(lex, FORMAT_TOKENS)
.group(action -> {
ProtoConstant constant = new ProtoConstant();
action
.map(TokenType.JAVADOC, token -> {
constant.javadocs(((CharSequenceBlockToken) token).value());
}, TokenTaskBuilder::asOptional)
.map(TokenType.IDENTIFIER, token -> {
constant.name(((CharSequenceToken) token).value());
})
.skipClosure(TokenType.LPAREN, TokenType.RPAREN, true)
.skipClosure(TokenType.LSCOPE, TokenType.RSCOPE, true)
.map(endMarkers::contains, $ -> {
// this part will probably fail for the last entry for enum without end (,;)
if (constant.isComplete()) {
map.put(constant.name(), constant.javadocs());
}
});
}, TokenTaskBuilder::asRepeatable)
.executeOrThrow();
*/
return map;
}
@Override
protected void insert(SearchMetadata metadata, StringBuilder builder) {
this.javadocsPerConstant = parseConstantJavadocs(metadata.replacedContent());
super.insert(metadata, builder);
}
@Override
protected void rewriteJavadocs(Holder.Reference<VillagerProfession> reference, String replacedContent, String indent, StringBuilder builder) {
String constantName = this.rewriteFieldName(reference);
if (this.javadocsPerConstant.containsKey(constantName)) {
CharSequenceBlockToken token = this.javadocsPerConstant.get(constantName);
builder.append(indent).append(replacedContent, token.pos(), token.endPos()).append('\n');
}
}
}

View File

@ -0,0 +1,6 @@
@ApiStatus.Experimental
@NullMarked
package io.papermc.generator.rewriter.types.simple.trial;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;

View File

@ -0,0 +1,48 @@
package io.papermc.generator.rewriter.utils;
import io.papermc.generator.utils.experimental.SingleFlagHolder;
import io.papermc.typewriter.context.ImportCollector;
import io.papermc.typewriter.util.ClassHelper;
import java.lang.annotation.Annotation;
import org.bukkit.MinecraftExperimental;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
@NullMarked
public final class Annotations {
public static String annotation(Class<? extends Annotation> clazz, ImportCollector collector) {
return "@%s".formatted(collector.getShortName(clazz));
}
public static String annotationStyle(Class<? extends Annotation> clazz) {
return "@%s".formatted(ClassHelper.retrieveFullNestedName(clazz));
}
public static String annotation(Class<? extends Annotation> clazz, ImportCollector collector, String param, String value) {
String annotation = annotation(clazz, collector);
if (value.isEmpty()) {
return annotation;
}
return "%s(%s = %s)".formatted(annotation, param, value);
}
public static String annotation(Class<? extends Annotation> clazz, ImportCollector collector, String value) {
String annotation = annotation(clazz, collector);
if (value.isEmpty()) {
return annotation;
}
return "%s(%s)".formatted(annotation, value);
}
public static void experimentalAnnotations(StringBuilder builder, String indent, ImportCollector importCollector, SingleFlagHolder requiredFeature) {
builder.append(indent).append(annotation(MinecraftExperimental.class, importCollector, "%s.%s".formatted(
importCollector.getShortName(MinecraftExperimental.Requires.class, false), requiredFeature.asAnnotationMember().name()
))).append('\n');
builder.append(indent).append(annotation(ApiStatus.Experimental.class, importCollector)).append('\n');
}
private Annotations() {
}
}

View File

@ -0,0 +1,110 @@
package io.papermc.generator.rewriter.utils;
import io.papermc.generator.Main;
import io.papermc.generator.Rewriters;
import io.papermc.generator.rewriter.registration.PaperPatternSourceSetRewriter;
import io.papermc.paper.generated.GeneratedFrom;
import io.papermc.typewriter.SourceFile;
import io.papermc.typewriter.SourceRewriter;
import io.papermc.typewriter.context.FileMetadata;
import io.papermc.typewriter.context.IndentUnit;
import io.papermc.typewriter.parser.StringReader;
import io.papermc.typewriter.replace.CommentMarker;
import io.papermc.typewriter.replace.SearchReplaceRewriter;
import io.papermc.typewriter.replace.SearchReplaceRewriterBase;
import net.minecraft.SharedConstants;
import java.io.IOException;
import java.io.LineNumberReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import static io.papermc.typewriter.replace.CommentMarker.EMPTY_MARKER;
public class ScanOldGeneratedSourceCode {
private static final String CURRENT_VERSION;
static {
Main.bootStrap(false);
CURRENT_VERSION = SharedConstants.getCurrentVersion().getId();
}
public static void main(String[] args) throws IOException {
PaperPatternSourceSetRewriter apiSourceSet = new PaperPatternSourceSetRewriter();
PaperPatternSourceSetRewriter serverSourceSet = new PaperPatternSourceSetRewriter();
Rewriters.bootstrap(apiSourceSet, serverSourceSet);
checkOutdated(apiSourceSet, Path.of(args[0], "src/main/java"));
checkOutdated(serverSourceSet, Path.of(args[1], "src/main/java"));
}
private static void checkOutdated(PaperPatternSourceSetRewriter sourceSetRewriter, Path sourceSet) throws IOException {
IndentUnit globalIndentUnit = sourceSetRewriter.getMetadata().indentUnit();
for (Map.Entry<SourceFile, SourceRewriter> entry : sourceSetRewriter.getRewriters().entrySet()) {
SourceRewriter rewriter = entry.getValue();
if (!(rewriter instanceof SearchReplaceRewriterBase srt) ||
srt.getRewriters().stream().noneMatch(SearchReplaceRewriter::hasGeneratedComment)) {
continue;
}
SourceFile file = entry.getKey();
IndentUnit indentUnit = file.metadata().flatMap(FileMetadata::indentUnit).orElse(globalIndentUnit);
Set<SearchReplaceRewriter> rewriters = new HashSet<>(srt.getRewriters());
try (LineNumberReader reader = new LineNumberReader(Files.newBufferedReader(sourceSet.resolve(file.path()), StandardCharsets.UTF_8))) {
while (true) {
String line = reader.readLine();
if (line == null) {
break;
}
if (line.isEmpty()) {
continue;
}
CommentMarker marker = srt.searchStartMarker(new StringReader(line), indentUnit, rewriters);
if (marker != EMPTY_MARKER) {
int startIndentSize = marker.indentSize();
if (startIndentSize % indentUnit.size() != 0) {
continue;
}
String nextLine = reader.readLine();
if (nextLine == null) {
break;
}
if (nextLine.isEmpty()) {
continue;
}
StringReader nextLineIterator = new StringReader(nextLine);
int indentSize = nextLineIterator.skipChars(indentUnit.character());
if (indentSize != startIndentSize) {
continue;
}
String generatedComment = "// %s ".formatted(Annotations.annotationStyle(GeneratedFrom.class));
if (nextLineIterator.trySkipString(generatedComment) && nextLineIterator.canRead()) {
String generatedVersion = nextLineIterator.getRemaining();
if (!CURRENT_VERSION.equals(generatedVersion)) {
throw new AssertionError(
"Code at line %d in %s is marked as being generated in version %s when the current version is %s".formatted(
reader.getLineNumber(), file.mainClass().canonicalName(), generatedVersion, CURRENT_VERSION)
);
}
if (!marker.owner().getOptions().multipleOperation()) {
if (rewriters.remove(marker.owner()) && rewriters.isEmpty()) {
break;
}
}
}
}
}
}
}
}
}

View File

@ -0,0 +1,50 @@
package io.papermc.generator.types;
import com.mojang.logging.LogUtils;
import com.squareup.javapoet.MethodSpec;
import io.papermc.generator.utils.Annotations;
import java.util.Arrays;
import org.jspecify.annotations.NullMarked;
import org.slf4j.Logger;
import static javax.lang.model.element.Modifier.PUBLIC;
@NullMarked
public abstract class OverriddenClassGenerator<T> extends SimpleGenerator {
private static final Logger LOGGER = LogUtils.getLogger();
protected final Class<? extends T> baseClass;
protected boolean printWarningOnMissingOverride;
protected OverriddenClassGenerator(Class<T> baseClass, String className, String packageName) {
super(className, packageName);
this.baseClass = baseClass;
}
public Class<? extends T> getBaseClass() {
return this.baseClass;
}
public MethodSpec.Builder createMethod(String name, Class<?>... parameterTypes) {
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(name)
.addModifiers(PUBLIC);
if (methodExists(name, parameterTypes)) {
methodBuilder.addAnnotation(Annotations.OVERRIDE);
} else {
if (this.printWarningOnMissingOverride) {
LOGGER.warn("Method {}#{}{} didn't override a known api method!", this.className, name, Arrays.toString(parameterTypes));
}
}
return methodBuilder;
}
protected boolean methodExists(String name, Class<?>... parameterTypes) {
try {
this.baseClass.getMethod(name, parameterTypes);
return true;
} catch (NoSuchMethodException e) {
return false;
}
}
}

View File

@ -0,0 +1,30 @@
package io.papermc.generator.types;
import com.squareup.javapoet.TypeSpec;
import io.papermc.generator.utils.Annotations;
import javax.lang.model.element.Modifier;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class SimpleEnumGenerator<T extends Enum<T>> extends SimpleGenerator {
private final Class<T> enumClass;
public SimpleEnumGenerator(Class<T> enumClass, String packageName) {
super(enumClass.getSimpleName(), packageName);
this.enumClass = enumClass;
}
@Override
protected TypeSpec getTypeSpec() {
TypeSpec.Builder typeBuilder = TypeSpec.enumBuilder(this.enumClass.getSimpleName())
.addModifiers(Modifier.PUBLIC)
.addAnnotations(Annotations.CLASS_HEADER);
for (T enumValue : this.enumClass.getEnumConstants()) {
typeBuilder.addEnumConstant(enumValue.name());
}
return typeBuilder.build();
}
}

View File

@ -0,0 +1,38 @@
package io.papermc.generator.types;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.TypeSpec;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import org.jspecify.annotations.NullMarked;
@NullMarked
public abstract class SimpleGenerator implements SourceGenerator {
public static final String INDENT_UNIT = " ";
protected final String className;
protected final String packageName;
protected SimpleGenerator(String className, String packageName) {
this.className = className;
this.packageName = packageName;
}
protected abstract TypeSpec getTypeSpec();
protected JavaFile.Builder file(JavaFile.Builder builder) {
return builder;
}
@Override
public void writeToFile(Path parent) throws IOException {
JavaFile.Builder builder = JavaFile.builder(this.packageName, this.getTypeSpec());
this.file(builder)
.indent(INDENT_UNIT)
.skipJavaLangImports(true);
builder.build().writeTo(parent, StandardCharsets.UTF_8);
}
}

View File

@ -0,0 +1,11 @@
package io.papermc.generator.types;
import java.io.IOException;
import java.nio.file.Path;
import org.jspecify.annotations.NullMarked;
@NullMarked
public interface SourceGenerator {
void writeToFile(Path parent) throws IOException;
}

View File

@ -0,0 +1,14 @@
package io.papermc.generator.types;
import com.squareup.javapoet.ClassName;
import org.jspecify.annotations.NullMarked;
@NullMarked
public final class Types {
public static final String BASE_PACKAGE = "org.bukkit.craftbukkit";
public static final ClassName CRAFT_BLOCK_DATA = ClassName.get(BASE_PACKAGE + ".block.data", "CraftBlockData");
public static final ClassName CRAFT_BLOCK = ClassName.get(BASE_PACKAGE + ".block", "CraftBlock");
}

View File

@ -0,0 +1,23 @@
package io.papermc.generator.types.craftblockdata;
import com.google.common.base.Preconditions;
import io.papermc.generator.types.SourceGenerator;
import io.papermc.generator.utils.BlockStateMapping;
import java.util.List;
import java.util.Map;
import net.minecraft.world.level.block.Block;
import org.bukkit.block.data.BlockData;
import org.jspecify.annotations.NullMarked;
@NullMarked
public final class CraftBlockDataBootstrapper {
public static void bootstrap(List<SourceGenerator> generators) {
for (Map.Entry<Class<? extends Block>, BlockStateMapping.BlockData> entry : BlockStateMapping.MAPPING.entrySet()) {
Class<? extends BlockData> api = BlockStateMapping.getBestSuitedApiClass(entry.getValue());
Preconditions.checkState(api != null, "Unknown custom BlockData api class for " + entry.getKey().getCanonicalName());
generators.add(new CraftBlockDataGenerator<>(entry.getKey(), entry.getValue(), api));
}
}
}

View File

@ -0,0 +1,243 @@
package io.papermc.generator.types.craftblockdata;
import com.google.common.base.CaseFormat;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.mojang.datafixers.util.Either;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.TypeSpec;
import io.papermc.generator.types.OverriddenClassGenerator;
import io.papermc.generator.types.Types;
import io.papermc.generator.types.craftblockdata.property.PropertyMaker;
import io.papermc.generator.types.craftblockdata.property.PropertyWriter;
import io.papermc.generator.types.craftblockdata.property.converter.ConverterBase;
import io.papermc.generator.types.craftblockdata.property.converter.Converters;
import io.papermc.generator.types.craftblockdata.property.holder.DataPropertyMaker;
import io.papermc.generator.types.craftblockdata.property.holder.VirtualField;
import io.papermc.generator.types.craftblockdata.property.holder.converter.DataConverter;
import io.papermc.generator.types.craftblockdata.property.holder.converter.DataConverters;
import io.papermc.generator.utils.Annotations;
import io.papermc.generator.utils.BlockStateMapping;
import io.papermc.generator.utils.CommonVariable;
import io.papermc.generator.utils.NamingManager;
import it.unimi.dsi.fastutil.Pair;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Map;
import java.util.function.BiConsumer;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.ChiseledBookShelfBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.Property;
import org.bukkit.Axis;
import org.bukkit.block.BlockFace;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Rail;
import org.jspecify.annotations.NullMarked;
import static io.papermc.generator.utils.NamingManager.keywordGet;
import static io.papermc.generator.utils.NamingManager.keywordGetSet;
import static javax.lang.model.element.Modifier.FINAL;
import static javax.lang.model.element.Modifier.PRIVATE;
import static javax.lang.model.element.Modifier.PUBLIC;
import static javax.lang.model.element.Modifier.STATIC;
@NullMarked
public class CraftBlockDataGenerator<T extends BlockData> extends OverriddenClassGenerator<T> {
private final Class<? extends Block> blockClass;
private final BlockStateMapping.BlockData blockData;
protected CraftBlockDataGenerator(Class<? extends Block> blockClass, BlockStateMapping.BlockData blockData, Class<T> baseClass) {
super(baseClass, blockData.implName(), Types.BASE_PACKAGE + ".block.impl");
this.blockClass = blockClass;
this.blockData = blockData;
this.printWarningOnMissingOverride = true;
}
// default keywords: get/set
// for single boolean property: get = is
// for indexed boolean property: get = has
private static final Map<Property<?>, NamingManager.AccessKeyword> FLUENT_KEYWORD = ImmutableMap.<Property<?>, NamingManager.AccessKeyword>builder()
.put(BlockStateProperties.ATTACH_FACE, keywordGetSet("getAttached", "setAttached")) // todo remove this once switch methods are gone
.put(BlockStateProperties.EYE, keywordGet("has"))
.put(BlockStateProperties.BERRIES, keywordGet("has")) // spigot method rename
// data holder keywords is only needed for the first property they hold
.put(ChiseledBookShelfBlock.SLOT_OCCUPIED_PROPERTIES.getFirst(), keywordGet("is"))
.buildOrThrow();
private static final Map<Property<?>, BiConsumer<ParameterSpec, MethodSpec.Builder>> SETTER_PRECONDITIONS = Map.of(
BlockStateProperties.FACING, (param, method) -> {
method.addStatement("$T.checkArgument($N.isCartesian(), $S)", Preconditions.class, param, "Invalid face, only cartesian face are allowed for this property!");
},
BlockStateProperties.HORIZONTAL_FACING, (param, method) -> {
method.addStatement("$1T.checkArgument($2N.isCartesian() && $2N.getModY() == 0, $3S)", Preconditions.class, param, "Invalid face, only cartesian horizontal face are allowed for this property!");
},
BlockStateProperties.FACING_HOPPER, (param, method) -> {
method.addStatement("$1T.checkArgument($2N.isCartesian() && $2N != $3T.UP, $4S)", Preconditions.class, param, BlockFace.class, "Invalid face, only cartesian face (excluding UP) are allowed for this property!");
},
BlockStateProperties.VERTICAL_DIRECTION, (param, method) -> {
method.addStatement("$T.checkArgument($N.getModY() != 0, $S)", Preconditions.class, param, "Invalid face, only vertical face are allowed for this property!");
},
BlockStateProperties.ROTATION_16, (param, method) -> {
method.addStatement("$1T.checkArgument($2N != $3T.SELF && $2N.getModY() == 0, $4S)", Preconditions.class, param, BlockFace.class, "Invalid face, only horizontal face are allowed for this property!");
},
BlockStateProperties.HORIZONTAL_AXIS, (param, method) -> {
method.addStatement("$1T.checkArgument($2N == $3T.X || $2N == $3T.Z, $4S)", Preconditions.class, param, Axis.class, "Invalid axis, only horizontal axis are allowed for this property!");
},
BlockStateProperties.RAIL_SHAPE_STRAIGHT, (param, method) -> {
method.addStatement("$1T.checkArgument($2N != $3T.NORTH_EAST && $2N != $3T.NORTH_WEST && $2N != $3T.SOUTH_EAST && $2N != $3T.SOUTH_WEST, $4S)", Preconditions.class, param, Rail.Shape.class, "Invalid rail shape, only straight rail are allowed for this property!");
}
);
private TypeSpec.Builder propertyHolder() {
TypeSpec.Builder typeBuilder = TypeSpec.classBuilder(this.className)
.addModifiers(PUBLIC)
.addAnnotation(Annotations.GENERATED_FROM)
.superclass(Types.CRAFT_BLOCK_DATA)
.addSuperinterface(this.baseClass);
ParameterSpec parameter = ParameterSpec.builder(BlockState.class, "state").build();
MethodSpec constructor = MethodSpec.constructorBuilder()
.addModifiers(PUBLIC)
.addParameter(parameter)
.addStatement("super($N)", parameter)
.build();
typeBuilder.addMethod(constructor);
return typeBuilder;
}
@Override
protected TypeSpec getTypeSpec() {
TypeSpec.Builder typeBuilder = this.propertyHolder();
for (Property<?> property : this.blockData.properties()) {
Pair<Class<?>, String> fieldName = PropertyWriter.referenceFieldFromVar(this.blockClass, property, this.blockData.propertyFields());
PropertyMaker propertyMaker = PropertyMaker.make(property);
final String varName;
if (this.blockData.propertyFields().containsKey(property)) {
// get the name from the local class or fallback to the generic BlockStateProperties constant name if not found
varName = this.blockData.propertyFields().get(property).getName();
} else {
varName = fieldName.right();
}
FieldSpec.Builder fieldBuilder = FieldSpec.builder(propertyMaker.getPropertyType(), varName, PRIVATE, STATIC, FINAL)
.initializer("$T.$L", fieldName.left(), fieldName.right());
FieldSpec field = fieldBuilder.build();
typeBuilder.addField(field);
ConverterBase converter = Converters.getOrDefault(property, propertyMaker);
Class<?> apiClass = converter.getApiType();
NamingManager.AccessKeyword accessKeyword = null;
if (apiClass == Boolean.TYPE) {
accessKeyword = keywordGet("is");
}
accessKeyword = FLUENT_KEYWORD.getOrDefault(property, accessKeyword);
NamingManager propertyNaming = new NamingManager(accessKeyword, CaseFormat.LOWER_UNDERSCORE, property.getName());
// get
{
MethodSpec.Builder methodBuilder = createMethod(propertyNaming.simpleGetterName(name -> !name.startsWith("is_") && !name.startsWith("has_")));
converter.convertGetter(methodBuilder, field);
methodBuilder.returns(apiClass);
typeBuilder.addMethod(methodBuilder.build());
}
// set
{
String paramName = propertyNaming.paramName(apiClass);
ParameterSpec parameter = ParameterSpec.builder(apiClass, paramName, FINAL).build();
MethodSpec.Builder methodBuilder = createMethod(propertyNaming.simpleSetterName(name -> !name.startsWith("is_")), apiClass).addParameter(parameter);
if (!apiClass.isPrimitive()) {
methodBuilder.addStatement("$T.checkArgument($N != null, $S)", Preconditions.class, parameter, "%s cannot be null!".formatted(paramName));
}
if (SETTER_PRECONDITIONS.containsKey(property)) {
SETTER_PRECONDITIONS.get(property).accept(parameter, methodBuilder);
}
converter.convertSetter(methodBuilder, field, parameter);
typeBuilder.addMethod(methodBuilder.build());
}
// extra
propertyMaker.addExtras(typeBuilder, field, this, propertyNaming);
}
for (Map.Entry<Either<Field, VirtualField>, Collection<Property<?>>> complexFields : this.blockData.complexPropertyFields().asMap().entrySet()) {
Either<Field, VirtualField> fieldData = complexFields.getKey();
Collection<Property<?>> properties = complexFields.getValue();
Property<?> firstProperty = properties.iterator().next();
PropertyMaker propertyMaker = PropertyMaker.make(firstProperty);
ConverterBase propertyConverter = Converters.getOrDefault(firstProperty, propertyMaker);
DataPropertyMaker dataPropertyMaker = DataPropertyMaker.make(properties, this.blockClass, fieldData);
FieldSpec field = dataPropertyMaker.getOrCreateField(this.blockData.propertyFields()).build();
typeBuilder.addField(field);
DataConverter converter = DataConverters.getOrThrow(dataPropertyMaker.getType());
Class<?> apiClass = propertyConverter.getApiType();
NamingManager.AccessKeyword accessKeyword = null;
if (apiClass == Boolean.TYPE) {
accessKeyword = NamingManager.keywordGet("has");
}
accessKeyword = FLUENT_KEYWORD.getOrDefault(firstProperty, accessKeyword);
NamingManager baseNaming = new NamingManager(accessKeyword, CaseFormat.UPPER_UNDERSCORE, dataPropertyMaker.getBaseName());
ParameterSpec indexParameter = ParameterSpec.builder(dataPropertyMaker.getIndexClass(), dataPropertyMaker.getIndexClass() == Integer.TYPE ? CommonVariable.INDEX : baseNaming.paramName(dataPropertyMaker.getIndexClass()), FINAL).build();
// get
{
MethodSpec.Builder methodBuilder = createMethod(baseNaming.simpleGetterName(name -> true), dataPropertyMaker.getIndexClass())
.addParameter(indexParameter);
if (!dataPropertyMaker.getIndexClass().isPrimitive()) {
methodBuilder.addStatement("$T.checkArgument($N != null, $S)", Preconditions.class, indexParameter, "%s cannot be null!".formatted(indexParameter.name));
}
converter.convertGetter(propertyConverter, methodBuilder, field, indexParameter);
methodBuilder.returns(apiClass);
typeBuilder.addMethod(methodBuilder.build());
}
// set
{
String paramName = baseNaming.paramName(apiClass);
ParameterSpec parameter = ParameterSpec.builder(apiClass, paramName, FINAL).build();
MethodSpec.Builder methodBuilder = createMethod(baseNaming.simpleSetterName(name -> true), dataPropertyMaker.getIndexClass(), apiClass)
.addParameter(indexParameter)
.addParameter(parameter);
if (!dataPropertyMaker.getIndexClass().isPrimitive()) {
methodBuilder.addStatement("$T.checkArgument($N != null, $S)", Preconditions.class, indexParameter, "%s cannot be null!".formatted(indexParameter.name));
}
if (!apiClass.isPrimitive()) {
methodBuilder.addStatement("$T.checkArgument($N != null, $S)", Preconditions.class, parameter, "%s cannot be null!".formatted(paramName));
}
if (SETTER_PRECONDITIONS.containsKey(firstProperty)) {
SETTER_PRECONDITIONS.get(firstProperty).accept(parameter, methodBuilder);
}
converter.convertSetter(propertyConverter, methodBuilder, field, indexParameter, parameter);
typeBuilder.addMethod(methodBuilder.build());
}
// extra
dataPropertyMaker.addExtras(typeBuilder, field, indexParameter, propertyConverter, this, baseNaming);
}
return typeBuilder.build();
}
}

View File

@ -0,0 +1,46 @@
package io.papermc.generator.types.craftblockdata.property;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import io.papermc.generator.utils.BlockStateMapping;
import net.minecraft.util.StringRepresentable;
import net.minecraft.world.level.block.state.properties.EnumProperty;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class EnumPropertyWriter<T extends Enum<T> & StringRepresentable> extends PropertyWriter<T> {
protected EnumPropertyWriter(EnumProperty<T> property) {
super(property);
}
@Override
public TypeName getPropertyType() {
if (this.property.getClass() == EnumProperty.class) { // exact match
return ParameterizedTypeName.get(this.property.getClass(), this.property.getValueClass());
}
return super.getPropertyType();
}
@Override
protected Class<?> processApiType() {
Class<?> apiClass = this.property.getValueClass();
apiClass = BlockStateMapping.ENUM_BRIDGE.get(apiClass);
if (apiClass == null) {
throw new IllegalStateException("Unknown enum type for " + this.property);
}
return apiClass;
}
@Override
public void convertGetter(MethodSpec.Builder method, FieldSpec field) {
method.addStatement("return " + this.rawGetExprent().formatted("$N"), field, this.getApiType());
}
@Override
public String rawGetExprent() {
return "this.get(%s, $T.class)";
}
}

View File

@ -0,0 +1,44 @@
package io.papermc.generator.types.craftblockdata.property;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
import io.papermc.generator.types.craftblockdata.CraftBlockDataGenerator;
import io.papermc.generator.types.craftblockdata.property.converter.Converters;
import io.papermc.generator.utils.NamingManager;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.IntegerProperty;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class IntegerPropertyWriter extends PropertyWriter<Integer> {
protected IntegerPropertyWriter(IntegerProperty property) {
super(property);
}
@Override
public void addExtras(TypeSpec.Builder builder, FieldSpec field, CraftBlockDataGenerator<?> generator, NamingManager naming) {
if (Converters.has(this.property)) {
return;
}
IntegerProperty property = (IntegerProperty) this.property;
if (property.min != 0 || property.getName().equals(BlockStateProperties.LEVEL.getName())) { // special case (levelled: composter)
MethodSpec.Builder methodBuilder = generator.createMethod(naming.getterName(name -> true).pre("Minimum").concat());
methodBuilder.addStatement("return $N.min", field);
methodBuilder.returns(this.getApiType());
builder.addMethod(methodBuilder.build());
}
{
MethodSpec.Builder methodBuilder = generator.createMethod(naming.getterName(name -> true).pre("Maximum").concat());
methodBuilder.addStatement("return $N.max", field);
methodBuilder.returns(this.getApiType());
builder.addMethod(methodBuilder.build());
}
}
}

View File

@ -0,0 +1,25 @@
package io.papermc.generator.types.craftblockdata.property;
import com.squareup.javapoet.TypeName;
import io.papermc.generator.types.craftblockdata.property.appender.AppenderBase;
import io.papermc.generator.types.craftblockdata.property.converter.ConverterBase;
import net.minecraft.world.level.block.state.properties.EnumProperty;
import net.minecraft.world.level.block.state.properties.IntegerProperty;
import net.minecraft.world.level.block.state.properties.Property;
import org.jspecify.annotations.NullMarked;
@NullMarked
public interface PropertyMaker extends ConverterBase, AppenderBase {
TypeName getPropertyType();
static <T extends Comparable<T>> PropertyMaker make(Property<T> property) {
if (property instanceof IntegerProperty intProperty) {
return new IntegerPropertyWriter(intProperty);
}
if (property instanceof EnumProperty<?> enumProperty) {
return new EnumPropertyWriter<>(enumProperty);
}
return new PropertyWriter<>(property);
}
}

View File

@ -0,0 +1,88 @@
package io.papermc.generator.types.craftblockdata.property;
import com.google.common.base.Suppliers;
import com.google.common.primitives.Primitives;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import io.papermc.generator.types.craftblockdata.CraftBlockDataGenerator;
import io.papermc.generator.types.craftblockdata.property.appender.PropertyAppenders;
import io.papermc.generator.utils.BlockStateMapping;
import io.papermc.generator.utils.NamingManager;
import it.unimi.dsi.fastutil.Pair;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Map;
import java.util.function.Supplier;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.Property;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class PropertyWriter<T extends Comparable<T>> implements PropertyMaker {
protected final Property<T> property;
private final Supplier<Class<?>> apiClassSupplier;
protected PropertyWriter(Property<T> property) {
this.property = property;
this.apiClassSupplier = Suppliers.memoize(this::processApiType);
}
@Override
public TypeName getPropertyType() {
return TypeName.get(this.property.getClass());
}
protected Class<?> processApiType() {
Class<T> apiClass = this.property.getValueClass();
if (Primitives.isWrapperType(apiClass)) {
apiClass = Primitives.unwrap(apiClass);
}
return apiClass;
}
@Override
public Class<?> getApiType() {
return this.apiClassSupplier.get();
}
@Override
public String rawSetExprent() {
return "this.set(%s, $N)";
}
@Override
public String rawGetExprent() {
return "this.get(%s)";
}
@Override
public void addExtras(TypeSpec.Builder builder, FieldSpec field, CraftBlockDataGenerator<?> generator, NamingManager naming) {
PropertyAppenders.ifPresent(this.property, appender -> appender.addExtras(builder, field, generator, naming));
}
public static Pair<Class<?>, String> referenceField(Class<? extends Block> from, Property<?> property, Map<Property<?>, Field> fields) {
Class<?> fieldAccess = from;
Field field = fields.get(property);
if (field == null || !Modifier.isPublic(field.getModifiers())) {
fieldAccess = BlockStateProperties.class;
field = BlockStateMapping.FALLBACK_GENERIC_FIELDS.get(property);
}
return Pair.of(fieldAccess, field.getName());
}
public static Pair<Class<?>, String> referenceFieldFromVar(Class<? extends Block> from, Property<?> property, Map<Property<?>, Field> fields) {
Class<?> fieldAccess = from;
Field field = fields.get(property);
Field genericField = BlockStateMapping.FALLBACK_GENERIC_FIELDS.get(property);
if (field == null || !Modifier.isPublic(field.getModifiers()) || !genericField.getType().equals(field.getType())) {
// field type can differ from BlockStateProperties constants (that's the case for the shulker box (#FACING) and the vault (#STATE)) ref: 1.20.5
// in that case fallback to the more accurate type to avoid compile error
fieldAccess = BlockStateProperties.class;
field = genericField;
}
return Pair.of(fieldAccess, field.getName());
}
}

View File

@ -0,0 +1,13 @@
package io.papermc.generator.types.craftblockdata.property.appender;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.TypeSpec;
import io.papermc.generator.types.craftblockdata.CraftBlockDataGenerator;
import io.papermc.generator.utils.NamingManager;
import org.jspecify.annotations.NullMarked;
@NullMarked
public interface AppenderBase {
void addExtras(TypeSpec.Builder builder, FieldSpec field, CraftBlockDataGenerator<?> generator, NamingManager naming);
}

View File

@ -0,0 +1,45 @@
package io.papermc.generator.types.craftblockdata.property.appender;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeSpec;
import io.papermc.generator.types.craftblockdata.CraftBlockDataGenerator;
import io.papermc.generator.utils.NamingManager;
import java.util.Set;
import net.minecraft.util.StringRepresentable;
import net.minecraft.world.level.block.state.properties.EnumProperty;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class EnumValuesAppender<T extends Enum<T> & StringRepresentable, A extends Enum<A>> implements PropertyAppender<T, A> {
private final EnumProperty<T> property;
private final Class<A> apiType;
private final String methodName;
public EnumValuesAppender(EnumProperty<T> property, Class<A> apiType, String methodName) {
this.property = property;
this.apiType = apiType;
this.methodName = methodName;
}
@Override
public EnumProperty<T> getProperty() {
return this.property;
}
@Override
public Class<A> getApiType() {
return this.apiType;
}
@Override
public void addExtras(TypeSpec.Builder builder, FieldSpec field, CraftBlockDataGenerator<?> generator, NamingManager naming) {
MethodSpec.Builder methodBuilder = generator.createMethod(this.methodName);
methodBuilder.addStatement("return this.getValues($N, $T.class)", field, this.apiType);
methodBuilder.returns(ParameterizedTypeName.get(Set.class, this.apiType));
builder.addMethod(methodBuilder.build());
}
}

View File

@ -0,0 +1,12 @@
package io.papermc.generator.types.craftblockdata.property.appender;
import net.minecraft.world.level.block.state.properties.Property;
import org.jspecify.annotations.NullMarked;
@NullMarked
public interface PropertyAppender<T extends Comparable<T>, A> extends AppenderBase {
Property<T> getProperty();
Class<A> getApiType();
}

View File

@ -0,0 +1,33 @@
package io.papermc.generator.types.craftblockdata.property.appender;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.Property;
import org.bukkit.Axis;
import org.bukkit.block.BlockFace;
import org.bukkit.block.data.Rail;
import org.jspecify.annotations.NullMarked;
@NullMarked
public final class PropertyAppenders {
private static final Map<Property<?>, AppenderBase> APPENDERS = Stream.of(
new EnumValuesAppender<>(BlockStateProperties.AXIS, Axis.class, "getAxes"),
new EnumValuesAppender<>(BlockStateProperties.HORIZONTAL_AXIS, Axis.class, "getAxes"),
new EnumValuesAppender<>(BlockStateProperties.FACING, BlockFace.class, "getFaces"),
new EnumValuesAppender<>(BlockStateProperties.HORIZONTAL_FACING, BlockFace.class, "getFaces"),
new EnumValuesAppender<>(BlockStateProperties.FACING_HOPPER, BlockFace.class, "getFaces"),
new EnumValuesAppender<>(BlockStateProperties.RAIL_SHAPE, Rail.Shape.class, "getShapes"),
new EnumValuesAppender<>(BlockStateProperties.RAIL_SHAPE_STRAIGHT, Rail.Shape.class, "getShapes"),
new EnumValuesAppender<>(BlockStateProperties.VERTICAL_DIRECTION, BlockFace.class, "getVerticalDirections")
).collect(Collectors.toUnmodifiableMap(PropertyAppender::getProperty, key -> key));
public static void ifPresent(Property<?> property, Consumer<AppenderBase> callback) {
if (APPENDERS.containsKey(property)) {
callback.accept(APPENDERS.get(property));
}
}
}

View File

@ -0,0 +1,13 @@
package io.papermc.generator.types.craftblockdata.property.converter;
import net.minecraft.world.level.block.state.properties.Property;
import org.jspecify.annotations.NullMarked;
@NullMarked
public interface Converter<T extends Comparable<T>, A> extends ConverterBase {
Property<T> getProperty();
@Override
Class<A> getApiType();
}

View File

@ -0,0 +1,24 @@
package io.papermc.generator.types.craftblockdata.property.converter;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import org.jspecify.annotations.NullMarked;
@NullMarked
public interface ConverterBase {
Class<?> getApiType();
default void convertSetter(MethodSpec.Builder method, FieldSpec field, ParameterSpec parameter) {
method.addStatement(this.rawSetExprent().formatted("$N"), field, parameter);
}
String rawSetExprent(); // this go on two layers which can be hard to follow refactor?
default void convertGetter(MethodSpec.Builder method, FieldSpec field) {
method.addStatement("return " + this.rawGetExprent().formatted("$N"), field);
}
String rawGetExprent();
}

View File

@ -0,0 +1,25 @@
package io.papermc.generator.types.craftblockdata.property.converter;
import io.papermc.generator.types.craftblockdata.property.PropertyMaker;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import net.minecraft.world.level.block.state.properties.Property;
import org.jspecify.annotations.NullMarked;
@NullMarked
public final class Converters {
private static final Map<Property<?>, ConverterBase> CONVERTERS = Stream.of(
new RotationConverter(),
new NoteConverter()
).collect(Collectors.toUnmodifiableMap(Converter::getProperty, key -> key));
public static ConverterBase getOrDefault(Property<?> property, PropertyMaker maker) {
return CONVERTERS.getOrDefault(property, maker);
}
public static boolean has(Property<?> property) {
return CONVERTERS.containsKey(property);
}
}

View File

@ -0,0 +1,37 @@
package io.papermc.generator.types.craftblockdata.property.converter;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.MethodSpec;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.Property;
import org.bukkit.Note;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class NoteConverter implements Converter<Integer, Note> {
@Override
public Property<Integer> getProperty() {
return BlockStateProperties.NOTE;
}
@Override
public Class<Note> getApiType() {
return Note.class;
}
@Override
public String rawSetExprent() {
return "this.set(%s, (int) $N.getId())";
}
@Override
public void convertGetter(MethodSpec.Builder method, FieldSpec field) {
method.addStatement("return " + this.rawGetExprent().formatted("$N"), this.getApiType(), field);
}
@Override
public String rawGetExprent() {
return "new $T(this.get(%s))";
}
}

View File

@ -0,0 +1,45 @@
package io.papermc.generator.types.craftblockdata.property.converter;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.Property;
import net.minecraft.world.level.block.state.properties.RotationSegment;
import org.bukkit.block.BlockFace;
import org.bukkit.util.Vector;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class RotationConverter implements Converter<Integer, BlockFace> {
private static final String DIRECTION_VAR = "dir";
private static final String ANGLE_VAR = "angle";
@Override
public Property<Integer> getProperty() {
return BlockStateProperties.ROTATION_16;
}
@Override
public Class<BlockFace> getApiType() {
return BlockFace.class;
}
@Override
public void convertSetter(MethodSpec.Builder method, FieldSpec field, ParameterSpec parameter) {
method.addStatement("$T $L = $N.getDirection()", Vector.class, DIRECTION_VAR, parameter);
method.addStatement("$1T $2L = ($1T) -$3T.toDegrees($3T.atan2($4L.getX(), $4L.getZ()))", Float.TYPE, ANGLE_VAR, Math.class, DIRECTION_VAR);
method.addStatement(this.rawSetExprent().formatted("$N", ANGLE_VAR), field, RotationSegment.class);
}
@Override
public String rawSetExprent() {
return "this.set(%s, $T.convertToSegment(%s))";
}
@Override
public String rawGetExprent() {
return "CraftBlockData.ROTATION_CYCLE[this.get(%s)]";
}
}

View File

@ -0,0 +1,7 @@
package io.papermc.generator.types.craftblockdata.property.holder;
public enum DataHolderType {
MAP,
LIST,
ARRAY
}

View File

@ -0,0 +1,31 @@
package io.papermc.generator.types.craftblockdata.property.holder;
import com.mojang.datafixers.util.Either;
import com.squareup.javapoet.FieldSpec;
import io.papermc.generator.types.craftblockdata.property.holder.appender.DataAppender;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Map;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.properties.Property;
import org.jspecify.annotations.NullMarked;
@NullMarked
public interface DataPropertyMaker extends DataAppender {
FieldSpec.Builder getOrCreateField(Map<Property<?>, Field> fields);
Class<?> getIndexClass();
@Override
DataHolderType getType();
String getBaseName();
static DataPropertyMaker make(Collection<? extends Property<?>> properties, Class<? extends Block> blockClass, Either<Field, VirtualField> fieldData) {
return fieldData.map(
field -> new DataPropertyWriter(field, properties, blockClass),
virtualField -> new VirtualDataPropertyWriter(virtualField, properties, blockClass)
);
}
}

View File

@ -0,0 +1,164 @@
package io.papermc.generator.types.craftblockdata.property.holder;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import io.papermc.generator.types.Types;
import io.papermc.generator.types.craftblockdata.CraftBlockDataGenerator;
import io.papermc.generator.types.craftblockdata.property.converter.ConverterBase;
import io.papermc.generator.types.craftblockdata.property.holder.appender.DataAppenders;
import io.papermc.generator.utils.BlockStateMapping;
import io.papermc.generator.utils.ClassHelper;
import io.papermc.generator.utils.CommonVariable;
import io.papermc.generator.utils.NamingManager;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import net.minecraft.core.Direction;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.ChiseledBookShelfBlock;
import net.minecraft.world.level.block.MossyCarpetBlock;
import net.minecraft.world.level.block.WallBlock;
import net.minecraft.world.level.block.state.properties.Property;
import org.bukkit.block.BlockFace;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.jspecify.annotations.NullMarked;
import static javax.lang.model.element.Modifier.FINAL;
import static javax.lang.model.element.Modifier.PRIVATE;
import static javax.lang.model.element.Modifier.STATIC;
@NullMarked
public class DataPropertyWriter extends DataPropertyWriterBase {
private record FieldKey(Class<? extends Block> blockClass, String fieldName) {
}
private static FieldKey key(Class<? extends Block> blockClass, String fieldName) {
return new FieldKey(blockClass, fieldName);
}
private static final Map<String, String> FIELD_TO_BASE_NAME = Map.of(
"PROPERTY_BY_DIRECTION", "FACE"
);
private static final Map<FieldKey, String> FIELD_TO_BASE_NAME_SPECIFICS = Map.of(
key(ChiseledBookShelfBlock.class, "SLOT_OCCUPIED_PROPERTIES"), "SLOT_OCCUPIED",
key(MossyCarpetBlock.class, "PROPERTY_BY_DIRECTION"), "HEIGHT",
key(WallBlock.class, "PROPERTY_BY_DIRECTION"), "HEIGHT"
);
protected final Field field;
protected @MonotonicNonNull DataHolderType type;
protected @MonotonicNonNull Class<?> indexClass, internalIndexClass;
protected @MonotonicNonNull TypeName fieldType;
protected DataPropertyWriter(Field field, Collection<? extends Property<?>> properties, Class<? extends Block> blockClass) {
super(properties, blockClass);
this.field = field;
this.computeTypes(field);
}
protected void computeTypes(Field field) {
this.fieldType = TypeName.get(field.getGenericType());
if (field.getType().isArray()) {
this.type = DataHolderType.ARRAY;
this.indexClass = Integer.TYPE;
} else if (List.class.isAssignableFrom(field.getType())) {
this.type = DataHolderType.LIST;
this.indexClass = Integer.TYPE;
} else if (Map.class.isAssignableFrom(field.getType()) && field.getGenericType() instanceof ParameterizedType complexType) {
this.type = DataHolderType.MAP;
this.internalIndexClass = ClassHelper.eraseType(complexType.getActualTypeArguments()[0]);
if (this.internalIndexClass.isEnum()) {
this.indexClass = BlockStateMapping.ENUM_BRIDGE.getOrDefault(this.internalIndexClass, (Class<? extends Enum<?>>) this.internalIndexClass);
this.fieldType = ParameterizedTypeName.get(
ClassName.get(field.getType()),
ClassName.get(this.indexClass),
ClassName.get(complexType.getActualTypeArguments()[1])
);
} else {
this.indexClass = this.internalIndexClass;
}
} else {
throw new IllegalStateException("Don't know how to turn " + field + " into api");
}
}
@Override
public FieldSpec.Builder getOrCreateField(Map<Property<?>, Field> fields) {
FieldSpec.Builder fieldBuilder = FieldSpec.builder(this.fieldType, this.field.getName(), PRIVATE, STATIC, FINAL);
if (Modifier.isPublic(this.field.getModifiers())) {
// accessible phew
if (this.type == DataHolderType.MAP &&
this.internalIndexClass == Direction.class && this.indexClass == BlockFace.class) { // Direction -> BlockFace
// convert the key manually only this one is needed for now
fieldBuilder.initializer("$[$1T.$2L.entrySet().stream()\n.collect($3T.toMap($4L -> $5T.notchToBlockFace($4L.getKey()), $4L -> $4L.getValue()))$]",
this.blockClass, this.field.getName(), Collectors.class, CommonVariable.MAP_ENTRY, Types.CRAFT_BLOCK);
} else {
fieldBuilder.initializer("$T.$L", this.blockClass, this.field.getName());
}
} else {
if (this.type == DataHolderType.ARRAY || this.type == DataHolderType.LIST) {
CodeBlock.Builder code = CodeBlock.builder();
this.createSyntheticCollection(code, this.type == DataHolderType.ARRAY, fields);
fieldBuilder.initializer(code.build());
} else if (this.type == DataHolderType.MAP) {
CodeBlock.Builder code = CodeBlock.builder();
this.createSyntheticMap(code, this.indexClass, fields);
fieldBuilder.initializer(code.build());
}
}
return fieldBuilder;
}
@Override
public Class<?> getIndexClass() {
return this.indexClass;
}
@Override
public DataHolderType getType() {
return this.type;
}
@Override
public String getBaseName() {
String constantName = this.field.getName();
FieldKey key = key(this.blockClass, constantName);
if (FIELD_TO_BASE_NAME_SPECIFICS.containsKey(key)) {
return FIELD_TO_BASE_NAME_SPECIFICS.get(key);
}
if (FIELD_TO_BASE_NAME.containsKey(constantName)) {
return FIELD_TO_BASE_NAME.get(constantName);
}
return stripFieldAccessKeyword(constantName);
}
private static final List<String> CUSTOM_KEYWORD = List.of("HAS", "IS", "CAN");
private String stripFieldAccessKeyword(String name) {
for (String keyword : CUSTOM_KEYWORD) {
if (name.startsWith(keyword + "_")) {
return name.substring(keyword.length() + 1);
}
}
return name;
}
@Override
public void addExtras(TypeSpec.Builder builder, FieldSpec field, ParameterSpec indexParameter, ConverterBase childConverter, CraftBlockDataGenerator<?> generator, NamingManager baseNaming) {
DataAppenders.ifPresent(this.type, appender -> appender.addExtras(builder, field, indexParameter, childConverter, generator, baseNaming));
}
}

View File

@ -0,0 +1,66 @@
package io.papermc.generator.types.craftblockdata.property.holder;
import com.squareup.javapoet.CodeBlock;
import io.papermc.generator.types.craftblockdata.property.PropertyWriter;
import io.papermc.generator.utils.Formatting;
import it.unimi.dsi.fastutil.Pair;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.properties.Property;
import org.jspecify.annotations.NullMarked;
@NullMarked
public abstract class DataPropertyWriterBase implements DataPropertyMaker {
protected final Collection<? extends Property<?>> properties;
protected final Class<? extends Block> blockClass;
protected DataPropertyWriterBase(Collection<? extends Property<?>> properties, Class<? extends Block> blockClass) {
this.properties = properties;
this.blockClass = blockClass;
}
protected void createSyntheticCollection(CodeBlock.Builder code, boolean isArray, Map<Property<?>, Field> fields) {
if (isArray) {
code.add("{\n");
} else {
code.add("$T.of(\n", List.class);
}
code.indent();
Iterator<? extends Property<?>> it = this.properties.iterator();
while (it.hasNext()) {
Property<?> property = it.next();
Pair<Class<?>, String> fieldName = PropertyWriter.referenceField(this.blockClass, property, fields);
code.add("$T.$L", fieldName.left(), fieldName.right());
if (it.hasNext()) {
code.add(",");
}
code.add("\n");
}
code.unindent().add(isArray ? "}" : ")");
}
protected void createSyntheticMap(CodeBlock.Builder code, Class<?> indexClass, Map<Property<?>, Field> fields) {
// assume indexClass is an enum with its values matching the property names
code.add("$T.of(\n", Map.class).indent();
Iterator<? extends Property<?>> it = this.properties.iterator();
while (it.hasNext()) {
Property<?> property = it.next();
String name = Formatting.formatKeyAsField(property.getName());
Pair<Class<?>, String> fieldName = PropertyWriter.referenceField(this.blockClass, property, fields);
code.add("$T.$L, $T.$L", indexClass, name, fieldName.left(), fieldName.right());
if (it.hasNext()) {
code.add(",");
}
code.add("\n");
}
code.unindent().add(")");
}
@Override
public abstract Class<?> getIndexClass();
}

View File

@ -0,0 +1,99 @@
package io.papermc.generator.types.craftblockdata.property.holder;
import com.squareup.javapoet.ArrayTypeName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import io.papermc.generator.types.craftblockdata.CraftBlockDataGenerator;
import io.papermc.generator.types.craftblockdata.property.converter.ConverterBase;
import io.papermc.generator.utils.BlockStateMapping;
import io.papermc.generator.utils.NamingManager;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.properties.Property;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.jspecify.annotations.NullMarked;
import static javax.lang.model.element.Modifier.FINAL;
import static javax.lang.model.element.Modifier.PRIVATE;
import static javax.lang.model.element.Modifier.STATIC;
@NullMarked
public class VirtualDataPropertyWriter extends DataPropertyWriterBase {
private final VirtualField virtualField;
protected @MonotonicNonNull Class<?> indexClass;
protected @MonotonicNonNull TypeName fieldType;
protected VirtualDataPropertyWriter(VirtualField virtualField, Collection<? extends Property<?>> properties, Class<? extends Block> blockClass) {
super(properties, blockClass);
this.virtualField = virtualField;
this.computeTypes(virtualField);
}
protected void computeTypes(VirtualField virtualField) {
switch (virtualField.holderType()) {
case ARRAY -> {
this.indexClass = Integer.TYPE;
this.fieldType = ArrayTypeName.of(virtualField.valueType());
}
case LIST -> {
this.indexClass = Integer.TYPE;
this.fieldType = ParameterizedTypeName.get(List.class, virtualField.valueType());
}
case MAP -> {
if (virtualField.keyClass() != null) {
this.indexClass = virtualField.keyClass();
} else {
this.indexClass = this.properties.iterator().next().getValueClass();
if (this.indexClass.isEnum()) {
this.indexClass = BlockStateMapping.ENUM_BRIDGE.getOrDefault(this.indexClass, (Class<? extends Enum<?>>) this.indexClass);
}
}
this.fieldType = ParameterizedTypeName.get(Map.class, this.indexClass, virtualField.valueType());
}
}
}
@Override
public FieldSpec.Builder getOrCreateField(Map<Property<?>, Field> fields) {
FieldSpec.Builder fieldBuilder = FieldSpec.builder(this.fieldType, this.virtualField.name(), PRIVATE, STATIC, FINAL);
if (this.getType() == DataHolderType.ARRAY || this.getType() == DataHolderType.LIST) {
CodeBlock.Builder code = CodeBlock.builder();
this.createSyntheticCollection(code, this.getType() == DataHolderType.ARRAY, fields);
fieldBuilder.initializer(code.build());
} else if (this.getType() == DataHolderType.MAP) {
CodeBlock.Builder code = CodeBlock.builder();
this.createSyntheticMap(code, this.indexClass, fields);
fieldBuilder.initializer(code.build());
}
return fieldBuilder;
}
@Override
public Class<?> getIndexClass() {
return this.indexClass;
}
@Override
public DataHolderType getType() {
return this.virtualField.holderType();
}
@Override
public String getBaseName() {
return this.virtualField.baseName();
}
@Override
public void addExtras(TypeSpec.Builder builder, FieldSpec field, ParameterSpec indexParameter, ConverterBase converter, CraftBlockDataGenerator<?> generator, NamingManager baseNaming) {
}
}

View File

@ -0,0 +1,72 @@
package io.papermc.generator.types.craftblockdata.property.holder;
import com.google.common.base.Preconditions;
import com.google.common.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.List;
import net.minecraft.world.level.block.state.properties.Property;
import org.jetbrains.annotations.Contract;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public record VirtualField(
String name,
Type valueType,
DataHolderType holderType,
String baseName,
@Nullable Class<?> keyClass,
Collection<? extends Property<?>> values
) {
@Contract(value = "_, _, _, _ -> new", pure = true)
public static <T extends Property<? extends Comparable<?>>> VirtualField.FieldValue<T> createCollection(String name, TypeToken<T> valueType, boolean isArray, String baseName) {
return new VirtualField.FieldValue<>(name, valueType, isArray ? DataHolderType.ARRAY : DataHolderType.LIST, baseName, null);
}
@Contract(value = "_, _, _, _ -> new", pure = true)
public static <T extends Property<? extends Comparable<?>>> VirtualField.FieldValue<T> createCollection(String name, Class<T> valueType, boolean isArray, String baseName) {
return createCollection(name, TypeToken.of(valueType), isArray, baseName);
}
@Contract(value = "_, _, _, _ -> new", pure = true)
public static <T extends Property<? extends Comparable<?>>> VirtualField.FieldValue<T> createMap(String name, Class<?> keyClass, TypeToken<T> valueType, String baseName) {
return new VirtualField.FieldValue<>(name, valueType, DataHolderType.MAP, baseName, keyClass);
}
@Contract(value = "_, _, _, _ -> new", pure = true)
public static <T extends Property<? extends Comparable<?>>> VirtualField.FieldValue<T> createMap(String name, Class<?> keyClass, Class<T> valueType, String baseName) {
return createMap(name, keyClass, TypeToken.of(valueType), baseName);
}
public static class FieldValue<T extends Property<? extends Comparable<?>>> {
private final String name;
private final DataHolderType holderType;
private final TypeToken<T> valueTypeToken;
private final String baseName;
private final @Nullable Class<?> keyClass;
private @Nullable Collection<T> values;
public FieldValue(String name, TypeToken<T> valueTypeToken, DataHolderType holderType, String baseName, @Nullable Class<?> keyClass) {
this.name = name;
this.valueTypeToken = valueTypeToken;
this.holderType = holderType;
this.baseName = baseName;
this.keyClass = keyClass;
}
@Contract(value = "_ -> this", mutates = "this")
public FieldValue<T> withValues(Collection<T> properties) {
this.values = List.copyOf(properties);
return this;
}
public VirtualField make() {
Preconditions.checkState(this.values != null && !this.values.isEmpty(), "The field should doesn't have any content");
return new VirtualField(this.name, this.valueTypeToken.getType(), this.holderType, this.baseName, this.keyClass, this.values);
}
}
}

View File

@ -0,0 +1,54 @@
package io.papermc.generator.types.craftblockdata.property.holder.appender;
import com.google.common.collect.ImmutableSet;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeSpec;
import io.papermc.generator.types.craftblockdata.CraftBlockDataGenerator;
import io.papermc.generator.types.craftblockdata.property.converter.ConverterBase;
import io.papermc.generator.types.craftblockdata.property.holder.DataHolderType;
import io.papermc.generator.utils.CommonVariable;
import io.papermc.generator.utils.NamingManager;
import java.util.Set;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class ArrayAppender implements DataAppender {
@Override
public DataHolderType getType() {
return DataHolderType.ARRAY;
}
@Override
public void addExtras(TypeSpec.Builder builder, FieldSpec field, ParameterSpec indexParameter, ConverterBase childConverter, CraftBlockDataGenerator<?> generator, NamingManager baseNaming) {
if (childConverter.getApiType() == Boolean.TYPE) {
String collectVarName = baseNaming.getVariableNameWrapper().post("s").concat();
MethodSpec.Builder methodBuilder = generator.createMethod(baseNaming.getMethodNameWrapper().post("s").concat());
methodBuilder.addStatement("$T $L = $T.builder()", ParameterizedTypeName.get(ImmutableSet.Builder.class, Integer.class), collectVarName, ImmutableSet.class);
methodBuilder.beginControlFlow("for (int $1L = 0, len = $2N.length; $1L < len; $1L++)", CommonVariable.INDEX, field);
{
methodBuilder.beginControlFlow("if (" + childConverter.rawGetExprent().formatted("$N[$N]") + ")", field, indexParameter);
{
methodBuilder.addStatement("$L.add($L)", collectVarName, CommonVariable.INDEX);
}
methodBuilder.endControlFlow();
}
methodBuilder.endControlFlow();
methodBuilder.addStatement("return $L.build()", collectVarName);
methodBuilder.returns(ParameterizedTypeName.get(Set.class, Integer.class));
builder.addMethod(methodBuilder.build());
}
{
MethodSpec.Builder methodBuilder = generator.createMethod(baseNaming.getMethodNameWrapper().pre("Maximum").post("s").concat());
methodBuilder.addStatement("return $N.length", field);
methodBuilder.returns(Integer.TYPE);
builder.addMethod(methodBuilder.build());
}
}
}

View File

@ -0,0 +1,18 @@
package io.papermc.generator.types.craftblockdata.property.holder.appender;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.TypeSpec;
import io.papermc.generator.types.craftblockdata.CraftBlockDataGenerator;
import io.papermc.generator.types.craftblockdata.property.converter.ConverterBase;
import io.papermc.generator.types.craftblockdata.property.holder.DataHolderType;
import io.papermc.generator.utils.NamingManager;
import org.jspecify.annotations.NullMarked;
@NullMarked
public interface DataAppender {
DataHolderType getType();
void addExtras(TypeSpec.Builder builder, FieldSpec field, ParameterSpec indexParameter, ConverterBase converter, CraftBlockDataGenerator<?> generator, NamingManager baseNaming);
}

View File

@ -0,0 +1,24 @@
package io.papermc.generator.types.craftblockdata.property.holder.appender;
import io.papermc.generator.types.craftblockdata.property.holder.DataHolderType;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.jspecify.annotations.NullMarked;
@NullMarked
public final class DataAppenders {
private static final Map<DataHolderType, DataAppender> APPENDERS = Stream.of(
new ArrayAppender(),
new ListAppender(),
new MapAppender()
).collect(Collectors.toUnmodifiableMap(DataAppender::getType, key -> key));
public static void ifPresent(DataHolderType type, Consumer<DataAppender> callback) {
if (APPENDERS.containsKey(type)) {
callback.accept(APPENDERS.get(type));
}
}
}

View File

@ -0,0 +1,61 @@
package io.papermc.generator.types.craftblockdata.property.holder.appender;
import com.google.common.collect.ImmutableSet;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeSpec;
import io.papermc.generator.types.craftblockdata.CraftBlockDataGenerator;
import io.papermc.generator.types.craftblockdata.property.converter.ConverterBase;
import io.papermc.generator.types.craftblockdata.property.holder.DataHolderType;
import io.papermc.generator.utils.CommonVariable;
import io.papermc.generator.utils.NamingManager;
import java.util.Map;
import java.util.Set;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class ListAppender implements DataAppender {
private static final Map<String, String> METHOD_BASE_RENAMES = Map.of(
"SlotOccupied", "OccupiedSlot"
);
@Override
public DataHolderType getType() {
return DataHolderType.LIST;
}
@Override
public void addExtras(TypeSpec.Builder builder, FieldSpec field, ParameterSpec indexParameter, ConverterBase childConverter, CraftBlockDataGenerator<?> generator, NamingManager baseNaming) {
NamingManager.NameWrapper methodName = NamingManager.NameWrapper.wrap("get", METHOD_BASE_RENAMES.getOrDefault(baseNaming.getMethodBaseName(), baseNaming.getMethodBaseName()));
if (childConverter.getApiType() == Boolean.TYPE) {
String collectVarName = baseNaming.getVariableNameWrapper().post("s").concat();
MethodSpec.Builder methodBuilder = generator.createMethod(methodName.post("s").concat());
methodBuilder.addStatement("$T $L = $T.builder()", ParameterizedTypeName.get(ImmutableSet.Builder.class, Integer.class), collectVarName, ImmutableSet.class);
methodBuilder.beginControlFlow("for (int $1L = 0, size = $2N.size(); $1L < size; $1L++)", CommonVariable.INDEX, field);
{
methodBuilder.beginControlFlow("if (" + childConverter.rawGetExprent().formatted("$N.get($N)") + ")", field, indexParameter);
{
methodBuilder.addStatement("$L.add($L)", collectVarName, CommonVariable.INDEX);
}
methodBuilder.endControlFlow();
}
methodBuilder.endControlFlow();
methodBuilder.addStatement("return $L.build()", collectVarName);
methodBuilder.returns(ParameterizedTypeName.get(Set.class, Integer.class));
builder.addMethod(methodBuilder.build());
}
{
MethodSpec.Builder methodBuilder = generator.createMethod(methodName.pre("Maximum").post("s").concat());
methodBuilder.addStatement("return $N.size()", field);
methodBuilder.returns(Integer.TYPE);
builder.addMethod(methodBuilder.build());
}
}
}

View File

@ -0,0 +1,85 @@
package io.papermc.generator.types.craftblockdata.property.holder.appender;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeSpec;
import io.papermc.generator.types.craftblockdata.CraftBlockDataGenerator;
import io.papermc.generator.types.craftblockdata.property.converter.ConverterBase;
import io.papermc.generator.types.craftblockdata.property.holder.DataHolderType;
import io.papermc.generator.utils.CommonVariable;
import io.papermc.generator.utils.NamingManager;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import org.bukkit.block.BlockFace;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.MultipleFacing;
import org.bukkit.block.data.type.RedstoneWire;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class MapAppender implements DataAppender {
private static final Map<String, String> INDEX_NAMES = ImmutableMap.<String, String>builder()
.put(BlockFace.class.getSimpleName(), "Face")
.buildOrThrow();
// no real rule here some has some don't mossy carpet and wall could have it
private static final Set<Class<? extends BlockData>> HAS_ALLOWED_METHOD = Set.of(
MultipleFacing.class,
RedstoneWire.class
);
private static boolean supportsExtraMethod(Class<? extends BlockData> clazz) {
for (Class<? extends BlockData> supported : HAS_ALLOWED_METHOD) {
if (supported.isAssignableFrom(clazz)) {
return true;
}
}
return false;
}
@Override
public DataHolderType getType() {
return DataHolderType.MAP;
}
@Override
public void addExtras(TypeSpec.Builder builder, FieldSpec field, ParameterSpec indexParameter, ConverterBase childConverter, CraftBlockDataGenerator<?> generator, NamingManager baseNaming) {
if (childConverter.getApiType() == Boolean.TYPE) {
String collectVarName = baseNaming.getVariableNameWrapper().post("s").concat();
MethodSpec.Builder methodBuilder = generator.createMethod(baseNaming.getMethodNameWrapper().post("s").concat());
methodBuilder.addStatement("$T $L = $T.builder()", ParameterizedTypeName.get(ClassName.get(ImmutableSet.Builder.class), indexParameter.type), collectVarName, ImmutableSet.class);
methodBuilder.beginControlFlow("for ($T $N : $N.entrySet())", ParameterizedTypeName.get(ClassName.get(Map.Entry.class), indexParameter.type, ClassName.get(BooleanProperty.class)), CommonVariable.MAP_ENTRY, field);
{
methodBuilder.beginControlFlow("if (" + childConverter.rawGetExprent().formatted("$L.getValue()") + ")", CommonVariable.MAP_ENTRY);
{
methodBuilder.addStatement("$L.add($N.getKey())", collectVarName, CommonVariable.MAP_ENTRY);
}
methodBuilder.endControlFlow();
}
methodBuilder.endControlFlow();
methodBuilder.addStatement("return $L.build()", collectVarName);
methodBuilder.returns(ParameterizedTypeName.get(ClassName.get(Set.class), indexParameter.type));
builder.addMethod(methodBuilder.build());
}
if (supportsExtraMethod(generator.getBaseClass()) &&
indexParameter.type instanceof ClassName className && !className.isPrimitive() && !className.isBoxedPrimitive()) {
NamingManager.NameWrapper indexNaming = NamingManager.NameWrapper.wrap("get", INDEX_NAMES.getOrDefault(className.simpleName(), className.simpleName()));
MethodSpec.Builder methodBuilder = generator.createMethod(indexNaming.pre("Allowed").post("s").concat());
methodBuilder.addStatement("return $T.unmodifiableSet($N.keySet())", Collections.class, field);
methodBuilder.returns(ParameterizedTypeName.get(ClassName.get(Set.class), className));
builder.addMethod(methodBuilder.build());
}
}
}

View File

@ -0,0 +1,32 @@
package io.papermc.generator.types.craftblockdata.property.holder.converter;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import io.papermc.generator.types.craftblockdata.property.EnumPropertyWriter;
import io.papermc.generator.types.craftblockdata.property.converter.ConverterBase;
import io.papermc.generator.types.craftblockdata.property.holder.DataHolderType;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class ArrayConverter implements DataConverter {
@Override
public DataHolderType getType() {
return DataHolderType.ARRAY;
}
@Override
public void convertSetter(ConverterBase childConverter, MethodSpec.Builder method, FieldSpec field, ParameterSpec indexParameter, ParameterSpec parameter) {
method.addStatement(childConverter.rawSetExprent().formatted("$N[$N]"), field, indexParameter, parameter);
}
@Override
public void convertGetter(ConverterBase childConverter, MethodSpec.Builder method, FieldSpec field, ParameterSpec indexParameter) {
if (childConverter instanceof EnumPropertyWriter<?> enumConverter) {
method.addStatement("return " + childConverter.rawGetExprent().formatted("$N[$N]"), field, indexParameter, enumConverter.getApiType());
} else {
method.addStatement("return " + childConverter.rawGetExprent().formatted("$N[$N]"), field, indexParameter);
}
}
}

View File

@ -0,0 +1,18 @@
package io.papermc.generator.types.craftblockdata.property.holder.converter;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import io.papermc.generator.types.craftblockdata.property.converter.ConverterBase;
import io.papermc.generator.types.craftblockdata.property.holder.DataHolderType;
import org.jspecify.annotations.NullMarked;
@NullMarked
public interface DataConverter {
DataHolderType getType();
void convertSetter(ConverterBase childConverter, MethodSpec.Builder method, FieldSpec field, ParameterSpec indexParameter, ParameterSpec parameter);
void convertGetter(ConverterBase childConverter, MethodSpec.Builder method, FieldSpec field, ParameterSpec indexParameter);
}

View File

@ -0,0 +1,25 @@
package io.papermc.generator.types.craftblockdata.property.holder.converter;
import io.papermc.generator.types.craftblockdata.property.holder.DataHolderType;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.jspecify.annotations.NullMarked;
@NullMarked
public final class DataConverters {
private static final Map<DataHolderType, DataConverter> CONVERTERS = Stream.of(
new ArrayConverter(),
new ListConverter(),
new MapConverter()
).collect(Collectors.toUnmodifiableMap(DataConverter::getType, key -> key));
public static DataConverter getOrThrow(DataHolderType type) {
DataConverter converter = CONVERTERS.get(type);
if (converter == null) {
throw new IllegalStateException("Cannot handle data holder type: " + type);
}
return converter;
}
}

View File

@ -0,0 +1,32 @@
package io.papermc.generator.types.craftblockdata.property.holder.converter;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import io.papermc.generator.types.craftblockdata.property.EnumPropertyWriter;
import io.papermc.generator.types.craftblockdata.property.converter.ConverterBase;
import io.papermc.generator.types.craftblockdata.property.holder.DataHolderType;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class ListConverter implements DataConverter {
@Override
public DataHolderType getType() {
return DataHolderType.LIST;
}
@Override
public void convertSetter(ConverterBase childConverter, MethodSpec.Builder method, FieldSpec field, ParameterSpec indexParameter, ParameterSpec parameter) {
method.addStatement(childConverter.rawSetExprent().formatted("$N.get($N)"), field, indexParameter, parameter);
}
@Override
public void convertGetter(ConverterBase childConverter, MethodSpec.Builder method, FieldSpec field, ParameterSpec indexParameter) {
if (childConverter instanceof EnumPropertyWriter<?> enumConverter) {
method.addStatement("return " + childConverter.rawGetExprent().formatted("$N.get($N)"), field, indexParameter, enumConverter.getApiType());
} else {
method.addStatement("return " + childConverter.rawGetExprent().formatted("$N.get($N)"), field, indexParameter);
}
}
}

View File

@ -0,0 +1,45 @@
package io.papermc.generator.types.craftblockdata.property.holder.converter;
import com.google.common.base.Preconditions;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import io.papermc.generator.types.craftblockdata.property.EnumPropertyWriter;
import io.papermc.generator.types.craftblockdata.property.converter.ConverterBase;
import io.papermc.generator.types.craftblockdata.property.holder.DataHolderType;
import java.util.stream.Collectors;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class MapConverter implements DataConverter {
private static final String PROPERTY_VAR = "property";
@Override
public DataHolderType getType() {
return DataHolderType.MAP;
}
@Override
public void convertSetter(ConverterBase childConverter, MethodSpec.Builder method, FieldSpec field, ParameterSpec indexParameter, ParameterSpec parameter) {
method.addStatement("$T $L = $N.get($N)", ((ParameterizedTypeName) field.type).typeArguments.get(1), PROPERTY_VAR, field, indexParameter);
method.addStatement("$T.checkArgument($N != null, $S, $N.keySet().stream().map($T::name).collect($T.joining($S)))",
Preconditions.class, PROPERTY_VAR, "Invalid " + indexParameter.name + ", only %s are allowed!", field, Enum.class, Collectors.class, ", ");
method.addStatement(childConverter.rawSetExprent().formatted("$L"), PROPERTY_VAR, parameter);
}
@Override
public void convertGetter(ConverterBase childConverter, MethodSpec.Builder method, FieldSpec field, ParameterSpec indexParameter) {
method.addStatement("$T $L = $N.get($N)", ((ParameterizedTypeName) field.type).typeArguments.get(1), PROPERTY_VAR, field, indexParameter);
method.addStatement("$T.checkArgument($N != null, $S, $N.keySet().stream().map($T::name).collect($T.joining($S)))",
Preconditions.class, PROPERTY_VAR, "Invalid " + indexParameter.name + ", only %s are allowed!", field, Enum.class, Collectors.class, ", ");
if (childConverter instanceof EnumPropertyWriter<?> enumConverter) {
method.addStatement("return " + childConverter.rawGetExprent().formatted("$L"), PROPERTY_VAR, enumConverter.getApiType());
} else {
method.addStatement("return " + childConverter.rawGetExprent().formatted("$L"), PROPERTY_VAR);
}
}
}

View File

@ -0,0 +1,91 @@
package io.papermc.generator.types.goal;
import com.destroystokyo.paper.entity.ai.GoalKey;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.TypeVariableName;
import io.github.classgraph.ClassGraph;
import io.github.classgraph.ScanResult;
import io.papermc.generator.types.SimpleGenerator;
import io.papermc.generator.utils.Annotations;
import io.papermc.generator.utils.Formatting;
import io.papermc.generator.utils.Javadocs;
import io.papermc.typewriter.util.ClassHelper;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Stream;
import net.minecraft.world.entity.ai.goal.Goal;
import net.minecraft.world.entity.ai.goal.GoalSelector;
import net.minecraft.world.entity.ai.goal.WrappedGoal;
import org.bukkit.NamespacedKey;
import org.bukkit.entity.Mob;
import org.jspecify.annotations.NullMarked;
import static javax.lang.model.element.Modifier.FINAL;
import static javax.lang.model.element.Modifier.PRIVATE;
import static javax.lang.model.element.Modifier.PUBLIC;
import static javax.lang.model.element.Modifier.STATIC;
@NullMarked
public class MobGoalGenerator extends SimpleGenerator {
private static final String CLASS_HEADER = Javadocs.getVersionDependentClassHeader("keys", "Mob Goals");
public MobGoalGenerator(String className, String packageName) {
super(className, packageName);
}
@Override
protected TypeSpec getTypeSpec() {
TypeVariableName type = TypeVariableName.get("T", Mob.class);
TypeSpec.Builder typeBuilder = TypeSpec.interfaceBuilder(this.className)
.addSuperinterface(ParameterizedTypeName.get(ClassName.get(com.destroystokyo.paper.entity.ai.Goal.class), type))
.addModifiers(PUBLIC)
.addTypeVariable(type)
.addAnnotations(Annotations.CLASS_HEADER)
.addJavadoc(CLASS_HEADER);
TypeName mobType = ParameterizedTypeName.get(ClassName.get(Class.class), type);
ParameterSpec keyParam = ParameterSpec.builder(String.class, "key", FINAL).build();
ParameterSpec typeParam = ParameterSpec.builder(mobType, "type", FINAL).build();
MethodSpec.Builder createMethod = MethodSpec.methodBuilder("create")
.addModifiers(PRIVATE, STATIC)
.addParameter(keyParam)
.addParameter(typeParam)
.addCode("return $T.of($N, $T.minecraft($N));", GoalKey.class, typeParam, NamespacedKey.class, keyParam)
.addTypeVariable(type)
.returns(ParameterizedTypeName.get(ClassName.get(GoalKey.class), type));
List<Class<Goal>> classes;
try (ScanResult scanResult = new ClassGraph().enableAllInfo().whitelistPackages("net.minecraft").scan()) {
classes = scanResult.getSubclasses(Goal.class.getName()).loadClasses(Goal.class);
}
Stream<GoalKey<Mob>> vanillaGoals = classes.stream()
.filter(clazz -> !java.lang.reflect.Modifier.isAbstract(clazz.getModifiers()))
.filter(clazz -> !clazz.isAnonymousClass() || ClassHelper.getTopLevelClass(clazz) != GoalSelector.class)
.filter(clazz -> !WrappedGoal.class.equals(clazz)) // TODO - properly fix
.map(MobGoalNames::getKey)
.sorted(Comparator.<GoalKey<?>, String>comparing(o -> o.getEntityClass().getSimpleName())
.thenComparing(vanillaGoalKey -> vanillaGoalKey.getNamespacedKey().getKey())
);
vanillaGoals.forEach(goalKey -> {
String keyPath = goalKey.getNamespacedKey().getKey();
String fieldName = Formatting.formatKeyAsField(keyPath);
TypeName typedKey = ParameterizedTypeName.get(GoalKey.class, goalKey.getEntityClass());
FieldSpec.Builder fieldBuilder = FieldSpec.builder(typedKey, fieldName, PUBLIC, STATIC, FINAL)
.initializer("$N($S, $T.class)", createMethod.build(), keyPath, goalKey.getEntityClass());
typeBuilder.addField(fieldBuilder.build());
});
return typeBuilder.addMethod(createMethod.build()).build();
}
}

View File

@ -0,0 +1,206 @@
package io.papermc.generator.types.goal;
import com.destroystokyo.paper.entity.RangedEntity;
import com.destroystokyo.paper.entity.ai.GoalKey;
import com.google.common.base.CaseFormat;
import io.papermc.generator.utils.Formatting;
import io.papermc.paper.entity.SchoolableFish;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import net.minecraft.world.entity.ai.goal.Goal;
import net.minecraft.world.entity.monster.RangedAttackMob;
import org.apache.commons.lang3.math.NumberUtils;
import org.bukkit.NamespacedKey;
import org.bukkit.entity.*;
import org.jspecify.annotations.NullMarked;
@NullMarked
public final class MobGoalNames { // todo sync with MobGoalHelper ideally this should not be duplicated
private static final Map<Class<? extends Goal>, Class<? extends Mob>> entityClassCache = new HashMap<>();
public static final Map<Class<? extends net.minecraft.world.entity.Mob>, Class<? extends Mob>> bukkitMap = new LinkedHashMap<>();
static {
//<editor-fold defaultstate="collapsed" desc="bukkitMap Entities">
bukkitMap.put(net.minecraft.world.entity.Mob.class, Mob.class);
bukkitMap.put(net.minecraft.world.entity.AgeableMob.class, Ageable.class);
bukkitMap.put(net.minecraft.world.entity.ambient.AmbientCreature.class, Ambient.class);
bukkitMap.put(net.minecraft.world.entity.animal.Animal.class, Animals.class);
bukkitMap.put(net.minecraft.world.entity.ambient.Bat.class, Bat.class);
bukkitMap.put(net.minecraft.world.entity.animal.Bee.class, Bee.class);
bukkitMap.put(net.minecraft.world.entity.monster.Blaze.class, Blaze.class);
bukkitMap.put(net.minecraft.world.entity.animal.Cat.class, Cat.class);
bukkitMap.put(net.minecraft.world.entity.monster.CaveSpider.class, CaveSpider.class);
bukkitMap.put(net.minecraft.world.entity.animal.Chicken.class, Chicken.class);
bukkitMap.put(net.minecraft.world.entity.animal.Cod.class, Cod.class);
bukkitMap.put(net.minecraft.world.entity.animal.Cow.class, Cow.class);
bukkitMap.put(net.minecraft.world.entity.PathfinderMob.class, Creature.class);
bukkitMap.put(net.minecraft.world.entity.monster.Creeper.class, Creeper.class);
bukkitMap.put(net.minecraft.world.entity.animal.Dolphin.class, Dolphin.class);
bukkitMap.put(net.minecraft.world.entity.monster.Drowned.class, Drowned.class);
bukkitMap.put(net.minecraft.world.entity.boss.enderdragon.EnderDragon.class, EnderDragon.class);
bukkitMap.put(net.minecraft.world.entity.monster.EnderMan.class, Enderman.class);
bukkitMap.put(net.minecraft.world.entity.monster.Endermite.class, Endermite.class);
bukkitMap.put(net.minecraft.world.entity.monster.Evoker.class, Evoker.class);
bukkitMap.put(net.minecraft.world.entity.animal.AbstractFish.class, Fish.class);
bukkitMap.put(net.minecraft.world.entity.animal.AbstractSchoolingFish.class, SchoolableFish.class);
bukkitMap.put(net.minecraft.world.entity.FlyingMob.class, Flying.class);
bukkitMap.put(net.minecraft.world.entity.animal.Fox.class, Fox.class);
bukkitMap.put(net.minecraft.world.entity.monster.Ghast.class, Ghast.class);
bukkitMap.put(net.minecraft.world.entity.monster.Giant.class, Giant.class);
bukkitMap.put(net.minecraft.world.entity.animal.AbstractGolem.class, Golem.class);
bukkitMap.put(net.minecraft.world.entity.monster.Guardian.class, Guardian.class);
bukkitMap.put(net.minecraft.world.entity.monster.ElderGuardian.class, ElderGuardian.class);
bukkitMap.put(net.minecraft.world.entity.animal.horse.Horse.class, Horse.class);
bukkitMap.put(net.minecraft.world.entity.animal.horse.AbstractHorse.class, AbstractHorse.class);
bukkitMap.put(net.minecraft.world.entity.animal.horse.AbstractChestedHorse.class, ChestedHorse.class);
bukkitMap.put(net.minecraft.world.entity.animal.horse.Donkey.class, Donkey.class);
bukkitMap.put(net.minecraft.world.entity.animal.horse.Mule.class, Mule.class);
bukkitMap.put(net.minecraft.world.entity.animal.horse.SkeletonHorse.class, SkeletonHorse.class);
bukkitMap.put(net.minecraft.world.entity.animal.horse.ZombieHorse.class, ZombieHorse.class);
bukkitMap.put(net.minecraft.world.entity.animal.camel.Camel.class, org.bukkit.entity.Camel.class);
bukkitMap.put(net.minecraft.world.entity.monster.AbstractIllager.class, Illager.class);
bukkitMap.put(net.minecraft.world.entity.monster.Illusioner.class, Illusioner.class);
bukkitMap.put(net.minecraft.world.entity.monster.SpellcasterIllager.class, Spellcaster.class);
bukkitMap.put(net.minecraft.world.entity.animal.IronGolem.class, IronGolem.class);
bukkitMap.put(net.minecraft.world.entity.animal.horse.Llama.class, Llama.class);
bukkitMap.put(net.minecraft.world.entity.animal.horse.TraderLlama.class, TraderLlama.class);
bukkitMap.put(net.minecraft.world.entity.monster.MagmaCube.class, MagmaCube.class);
bukkitMap.put(net.minecraft.world.entity.monster.Monster.class, Monster.class);
bukkitMap.put(net.minecraft.world.entity.monster.PatrollingMonster.class, Raider.class); // close enough
bukkitMap.put(net.minecraft.world.entity.animal.MushroomCow.class, MushroomCow.class);
bukkitMap.put(net.minecraft.world.entity.animal.Ocelot.class, Ocelot.class);
bukkitMap.put(net.minecraft.world.entity.animal.Panda.class, Panda.class);
bukkitMap.put(net.minecraft.world.entity.animal.Parrot.class, Parrot.class);
bukkitMap.put(net.minecraft.world.entity.animal.ShoulderRidingEntity.class, Parrot.class); // close enough
bukkitMap.put(net.minecraft.world.entity.monster.Phantom.class, Phantom.class);
bukkitMap.put(net.minecraft.world.entity.animal.Pig.class, Pig.class);
bukkitMap.put(net.minecraft.world.entity.monster.ZombifiedPiglin.class, PigZombie.class);
bukkitMap.put(net.minecraft.world.entity.monster.Pillager.class, Pillager.class);
bukkitMap.put(net.minecraft.world.entity.animal.PolarBear.class, PolarBear.class);
bukkitMap.put(net.minecraft.world.entity.animal.Pufferfish.class, PufferFish.class);
bukkitMap.put(net.minecraft.world.entity.animal.Rabbit.class, Rabbit.class);
bukkitMap.put(net.minecraft.world.entity.raid.Raider.class, Raider.class);
bukkitMap.put(net.minecraft.world.entity.monster.Ravager.class, Ravager.class);
bukkitMap.put(net.minecraft.world.entity.animal.Salmon.class, Salmon.class);
bukkitMap.put(net.minecraft.world.entity.animal.sheep.Sheep.class, Sheep.class);
bukkitMap.put(net.minecraft.world.entity.monster.Shulker.class, Shulker.class);
bukkitMap.put(net.minecraft.world.entity.monster.Silverfish.class, Silverfish.class);
bukkitMap.put(net.minecraft.world.entity.monster.Skeleton.class, Skeleton.class);
bukkitMap.put(net.minecraft.world.entity.monster.AbstractSkeleton.class, AbstractSkeleton.class);
bukkitMap.put(net.minecraft.world.entity.monster.Stray.class, Stray.class);
bukkitMap.put(net.minecraft.world.entity.monster.WitherSkeleton.class, WitherSkeleton.class);
bukkitMap.put(net.minecraft.world.entity.monster.Slime.class, Slime.class);
bukkitMap.put(net.minecraft.world.entity.animal.SnowGolem.class, Snowman.class);
bukkitMap.put(net.minecraft.world.entity.monster.Spider.class, Spider.class);
bukkitMap.put(net.minecraft.world.entity.animal.Squid.class, Squid.class);
bukkitMap.put(net.minecraft.world.entity.TamableAnimal.class, Tameable.class);
bukkitMap.put(net.minecraft.world.entity.animal.TropicalFish.class, TropicalFish.class);
bukkitMap.put(net.minecraft.world.entity.animal.Turtle.class, Turtle.class);
bukkitMap.put(net.minecraft.world.entity.monster.Vex.class, Vex.class);
bukkitMap.put(net.minecraft.world.entity.npc.Villager.class, Villager.class);
bukkitMap.put(net.minecraft.world.entity.npc.AbstractVillager.class, AbstractVillager.class);
bukkitMap.put(net.minecraft.world.entity.npc.WanderingTrader.class, WanderingTrader.class);
bukkitMap.put(net.minecraft.world.entity.monster.Vindicator.class, Vindicator.class);
bukkitMap.put(net.minecraft.world.entity.animal.WaterAnimal.class, WaterMob.class);
bukkitMap.put(net.minecraft.world.entity.monster.Witch.class, Witch.class);
bukkitMap.put(net.minecraft.world.entity.boss.wither.WitherBoss.class, Wither.class);
bukkitMap.put(net.minecraft.world.entity.animal.wolf.Wolf.class, Wolf.class);
bukkitMap.put(net.minecraft.world.entity.monster.Zombie.class, Zombie.class);
bukkitMap.put(net.minecraft.world.entity.monster.Husk.class, Husk.class);
bukkitMap.put(net.minecraft.world.entity.monster.ZombieVillager.class, ZombieVillager.class);
bukkitMap.put(net.minecraft.world.entity.monster.hoglin.Hoglin.class, Hoglin.class);
bukkitMap.put(net.minecraft.world.entity.monster.piglin.Piglin.class, Piglin.class);
bukkitMap.put(net.minecraft.world.entity.monster.piglin.AbstractPiglin.class, PiglinAbstract.class);
bukkitMap.put(net.minecraft.world.entity.monster.piglin.PiglinBrute.class, PiglinBrute.class);
bukkitMap.put(net.minecraft.world.entity.monster.Strider.class, Strider.class);
bukkitMap.put(net.minecraft.world.entity.monster.Zoglin.class, Zoglin.class);
bukkitMap.put(net.minecraft.world.entity.GlowSquid.class, GlowSquid.class);
bukkitMap.put(net.minecraft.world.entity.animal.axolotl.Axolotl.class, Axolotl.class);
bukkitMap.put(net.minecraft.world.entity.animal.goat.Goat.class, Goat.class);
bukkitMap.put(net.minecraft.world.entity.animal.frog.Frog.class, Frog.class);
bukkitMap.put(net.minecraft.world.entity.animal.frog.Tadpole.class, Tadpole.class);
bukkitMap.put(net.minecraft.world.entity.monster.warden.Warden.class, Warden.class);
bukkitMap.put(net.minecraft.world.entity.animal.allay.Allay.class, Allay.class);
bukkitMap.put(net.minecraft.world.entity.animal.sniffer.Sniffer.class, Sniffer.class);
bukkitMap.put(net.minecraft.world.entity.monster.breeze.Breeze.class, Breeze.class);
bukkitMap.put(net.minecraft.world.entity.animal.armadillo.Armadillo.class, Armadillo.class);
bukkitMap.put(net.minecraft.world.entity.monster.Bogged.class, Bogged.class);
bukkitMap.put(net.minecraft.world.entity.monster.creaking.Creaking.class, Creaking.class);
bukkitMap.put(net.minecraft.world.entity.animal.AgeableWaterCreature.class, Squid.class); // close enough
bukkitMap.put(net.minecraft.world.entity.animal.AbstractCow.class, AbstractCow.class);
//</editor-fold>
}
private static final Map<String, String> deobfuscationMap = new HashMap<>();
static {
// TODO these kinda should be checked on each release, in case obfuscation changes
deobfuscationMap.put("abstract_skeleton_1", "abstract_skeleton_melee");
}
private static String getPathName(String name) {
String pathName = name.substring(name.lastIndexOf('.') + 1);
boolean needDeobfMap = false;
// inner classes
int firstInnerDelimiter = pathName.indexOf('$');
if (firstInnerDelimiter != -1) {
String innerClassName = pathName.substring(firstInnerDelimiter + 1);
for (String nestedClass : innerClassName.split("\\$")) {
if (NumberUtils.isDigits(nestedClass)) {
needDeobfMap = true;
break;
}
}
if (!needDeobfMap) {
pathName = innerClassName;
}
pathName = pathName.replace('$', '_');
// mapped, wooo!
}
pathName = Formatting.stripWordOfCamelCaseName(pathName, "TargetGoal", true); // replace last? reverse search?
pathName = Formatting.stripWordOfCamelCaseName(pathName, "Goal", true);
pathName = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, pathName);
if (needDeobfMap && !deobfuscationMap.containsKey(pathName)) {
System.err.println("need to map " + name + " (" + pathName + ")");
}
// did we rename this key?
return deobfuscationMap.getOrDefault(pathName, pathName);
}
public static <T extends Mob> GoalKey<T> getKey(Class<? extends Goal> goalClass) {
String name = getPathName(goalClass.getName());
return GoalKey.of(getEntity(goalClass), NamespacedKey.minecraft(name));
}
private static <T extends Mob> Class<T> getEntity(Class<? extends Goal> goalClass) {
//noinspection unchecked
return (Class<T>) entityClassCache.computeIfAbsent(goalClass, key -> {
for (Constructor<?> ctor : key.getDeclaredConstructors()) {
for (Class<?> param : ctor.getParameterTypes()) {
if (net.minecraft.world.entity.Mob.class.isAssignableFrom(param)) {
//noinspection unchecked
return toBukkitClass((Class<? extends net.minecraft.world.entity.Mob>) param);
} else if (RangedAttackMob.class.isAssignableFrom(param)) {
return RangedEntity.class;
}
}
}
throw new RuntimeException("Can't figure out applicable entity for mob goal " + goalClass); // maybe just return Mob?
});
}
private static Class<? extends Mob> toBukkitClass(Class<? extends net.minecraft.world.entity.Mob> nmsClass) {
Class<? extends Mob> bukkitClass = bukkitMap.get(nmsClass);
if (bukkitClass == null) {
throw new RuntimeException("Can't figure out applicable bukkit entity for nms entity " + nmsClass); // maybe just return Mob?
}
return bukkitClass;
}
}

View File

@ -0,0 +1,133 @@
package io.papermc.generator.types.registry;
import com.google.common.base.Suppliers;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import io.papermc.generator.registry.RegistryEntry;
import io.papermc.generator.types.SimpleGenerator;
import io.papermc.generator.utils.Annotations;
import io.papermc.generator.utils.Formatting;
import io.papermc.generator.utils.Javadocs;
import io.papermc.generator.utils.experimental.ExperimentalCollector;
import io.papermc.generator.utils.experimental.SingleFlagHolder;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.TypedKey;
import java.util.Map;
import java.util.function.Supplier;
import javax.lang.model.SourceVersion;
import net.kyori.adventure.key.Key;
import net.minecraft.core.Holder;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.flag.FeatureElement;
import net.minecraft.world.flag.FeatureFlags;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import static com.squareup.javapoet.TypeSpec.classBuilder;
import static io.papermc.generator.utils.Annotations.EXPERIMENTAL_API_ANNOTATION;
import static io.papermc.generator.utils.Annotations.experimentalAnnotations;
import static javax.lang.model.element.Modifier.FINAL;
import static javax.lang.model.element.Modifier.PRIVATE;
import static javax.lang.model.element.Modifier.PUBLIC;
import static javax.lang.model.element.Modifier.STATIC;
@NullMarked
public class GeneratedKeyType<T> extends SimpleGenerator {
private final RegistryEntry<T> entry;
private final Supplier<Map<ResourceKey<T>, SingleFlagHolder>> experimentalKeys;
private final boolean isFilteredRegistry;
public GeneratedKeyType(String packageName, RegistryEntry<T> entry) {
super(entry.keyClassName().concat("Keys"), packageName);
this.entry = entry;
this.experimentalKeys = Suppliers.memoize(() -> ExperimentalCollector.collectDataDrivenElementIds(entry.registry()));
this.isFilteredRegistry = FeatureElement.FILTERED_REGISTRIES.contains(entry.registryKey());
}
private MethodSpec.Builder createMethod(TypeName returnType) {
boolean publicCreateKeyMethod = this.entry.allowCustomKeys();
ParameterSpec keyParam = ParameterSpec.builder(Key.class, "key", FINAL).build();
MethodSpec.Builder create = MethodSpec.methodBuilder("create")
.addModifiers(publicCreateKeyMethod ? PUBLIC : PRIVATE, STATIC)
.addParameter(keyParam)
.addCode("return $T.create($T.$L, $N);", TypedKey.class, RegistryKey.class, this.entry.registryKeyField(), keyParam)
.returns(returnType);
if (publicCreateKeyMethod) {
create.addJavadoc(Javadocs.CREATE_TYPED_KEY_JAVADOC, this.entry.apiClass(), this.entry.registryKey().location().toString());
}
return create;
}
private TypeSpec.Builder keyHolderType() {
return classBuilder(this.className)
.addModifiers(PUBLIC, FINAL)
.addJavadoc(Javadocs.getVersionDependentClassHeader("keys", "{@link $T#$L}"), RegistryKey.class, this.entry.registryKeyField())
.addAnnotations(Annotations.CLASS_HEADER)
.addMethod(MethodSpec.constructorBuilder()
.addModifiers(PRIVATE)
.build()
);
}
@Override
protected TypeSpec getTypeSpec() {
TypeName typedKeyType = ParameterizedTypeName.get(TypedKey.class, this.entry.apiClass());
TypeSpec.Builder typeBuilder = this.keyHolderType();
MethodSpec.Builder createMethod = this.createMethod(typedKeyType);
boolean allExperimental = true;
for (Holder.Reference<T> reference : this.entry.registry().listElements().sorted(Formatting.alphabeticKeyOrder(reference -> reference.key().location().getPath())).toList()) {
ResourceKey<T> key = reference.key();
String keyPath = key.location().getPath();
String fieldName = Formatting.formatKeyAsField(keyPath);
if (!SourceVersion.isIdentifier(fieldName) && this.entry.getFieldNames().containsKey(key)) {
fieldName = this.entry.getFieldNames().get(key);
}
FieldSpec.Builder fieldBuilder = FieldSpec.builder(typedKeyType, fieldName, PUBLIC, STATIC, FINAL)
.initializer("$N(key($S))", createMethod.build(), keyPath)
.addJavadoc(Javadocs.getVersionDependentField("{@code $L}"), key.location().toString());
SingleFlagHolder requiredFeature = this.getRequiredFeature(reference);
if (requiredFeature != null) {
fieldBuilder.addAnnotations(experimentalAnnotations(requiredFeature));
} else {
allExperimental = false;
}
typeBuilder.addField(fieldBuilder.build());
}
if (allExperimental) {
typeBuilder.addAnnotation(EXPERIMENTAL_API_ANNOTATION);
createMethod.addAnnotation(EXPERIMENTAL_API_ANNOTATION);
}
return typeBuilder.addMethod(createMethod.build()).build();
}
@Override
protected JavaFile.Builder file(JavaFile.Builder builder) {
return builder.addStaticImport(Key.class, "key");
}
protected @Nullable SingleFlagHolder getRequiredFeature(Holder.Reference<T> reference) {
if (this.isFilteredRegistry) {
// built-in registry
FeatureElement element = (FeatureElement) reference.value();
if (FeatureFlags.isExperimental(element.requiredFeatures())) {
return SingleFlagHolder.fromSet(element.requiredFeatures());
}
} else {
// data-driven registry
return this.experimentalKeys.get().get(reference.key());
}
return null;
}
}

View File

@ -0,0 +1,103 @@
package io.papermc.generator.types.registry;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import io.papermc.generator.Main;
import io.papermc.generator.registry.RegistryEntry;
import io.papermc.generator.types.SimpleGenerator;
import io.papermc.generator.utils.Annotations;
import io.papermc.generator.utils.Formatting;
import io.papermc.generator.utils.Javadocs;
import io.papermc.generator.utils.experimental.SingleFlagHolder;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.tag.TagKey;
import java.util.concurrent.atomic.AtomicBoolean;
import net.kyori.adventure.key.Key;
import org.jspecify.annotations.NullMarked;
import static com.squareup.javapoet.TypeSpec.classBuilder;
import static io.papermc.generator.utils.Annotations.EXPERIMENTAL_API_ANNOTATION;
import static io.papermc.generator.utils.Annotations.experimentalAnnotations;
import static javax.lang.model.element.Modifier.FINAL;
import static javax.lang.model.element.Modifier.PRIVATE;
import static javax.lang.model.element.Modifier.PUBLIC;
import static javax.lang.model.element.Modifier.STATIC;
@NullMarked
public class GeneratedTagKeyType extends SimpleGenerator {
private final RegistryEntry<?> entry;
public GeneratedTagKeyType(RegistryEntry<?> entry, String packageName) {
super(entry.keyClassName().concat("TagKeys"), packageName);
this.entry = entry;
}
private MethodSpec.Builder createMethod(TypeName returnType) {
boolean publicCreateKeyMethod = true; // tag lifecycle event exists
ParameterSpec keyParam = ParameterSpec.builder(Key.class, "key", FINAL).build();
MethodSpec.Builder create = MethodSpec.methodBuilder("create")
.addModifiers(publicCreateKeyMethod ? PUBLIC : PRIVATE, STATIC)
.addParameter(keyParam)
.addCode("return $T.create($T.$L, $N);", TagKey.class, RegistryKey.class, this.entry.registryKeyField(), keyParam)
.returns(returnType);
if (publicCreateKeyMethod) {
create.addAnnotation(EXPERIMENTAL_API_ANNOTATION); // TODO remove once not experimental
create.addJavadoc(Javadocs.CREATED_TAG_KEY_JAVADOC, this.entry.apiClass(), this.entry.registryKey().location().toString());
}
return create;
}
private TypeSpec.Builder keyHolderType() {
return classBuilder(this.className)
.addModifiers(PUBLIC, FINAL)
.addJavadoc(Javadocs.getVersionDependentClassHeader("tag keys", "{@link $T#$L}"), RegistryKey.class, this.entry.registryKeyField())
.addAnnotations(Annotations.CLASS_HEADER)
.addMethod(MethodSpec.constructorBuilder()
.addModifiers(PRIVATE)
.build()
);
}
@Override
protected TypeSpec getTypeSpec() {
TypeName tagKeyType = ParameterizedTypeName.get(TagKey.class, this.entry.apiClass());
TypeSpec.Builder typeBuilder = this.keyHolderType();
MethodSpec.Builder createMethod = this.createMethod(tagKeyType);
AtomicBoolean allExperimental = new AtomicBoolean(true);
this.entry.registry().listTagIds().sorted(Formatting.alphabeticKeyOrder(tagKey -> tagKey.location().getPath())).forEach(tagKey -> {
String fieldName = Formatting.formatKeyAsField(tagKey.location().getPath());
FieldSpec.Builder fieldBuilder = FieldSpec.builder(tagKeyType, fieldName, PUBLIC, STATIC, FINAL)
.initializer("$N(key($S))", createMethod.build(), tagKey.location().getPath())
.addJavadoc(Javadocs.getVersionDependentField("{@code $L}"), "#" + tagKey.location());
String featureFlagName = Main.EXPERIMENTAL_TAGS.get(tagKey);
if (featureFlagName != null) {
fieldBuilder.addAnnotations(experimentalAnnotations(SingleFlagHolder.fromName(featureFlagName)));
} else {
allExperimental.set(false);
}
typeBuilder.addField(fieldBuilder.build());
});
if (allExperimental.get()) {
typeBuilder.addAnnotation(EXPERIMENTAL_API_ANNOTATION);
createMethod.addAnnotation(EXPERIMENTAL_API_ANNOTATION);
} else {
typeBuilder.addAnnotation(EXPERIMENTAL_API_ANNOTATION); // TODO experimental API
}
return typeBuilder.addMethod(createMethod.build()).build();
}
@Override
protected JavaFile.Builder file(JavaFile.Builder builder) {
return builder.addStaticImport(Key.class, "key");
}
}

View File

@ -0,0 +1,48 @@
package io.papermc.generator.utils;
import com.squareup.javapoet.AnnotationSpec;
import io.papermc.generator.utils.experimental.SingleFlagHolder;
import io.papermc.paper.generated.GeneratedFrom;
import java.util.List;
import net.minecraft.SharedConstants;
import org.bukkit.MinecraftExperimental;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
@NullMarked
public final class Annotations {
public static List<AnnotationSpec> experimentalAnnotations(SingleFlagHolder requiredFeature) {
AnnotationSpec.Builder builder = AnnotationSpec.builder(MinecraftExperimental.class);
builder.addMember("value", "$T.$L", MinecraftExperimental.Requires.class, requiredFeature.asAnnotationMember().name());
return List.of(
AnnotationSpec.builder(ApiStatus.Experimental.class).build(),
builder.build()
);
}
public static AnnotationSpec suppressWarnings(String... values) {
AnnotationSpec.Builder builder = AnnotationSpec.builder(SuppressWarnings.class);
for (String value : values) {
builder.addMember("value", "$S", value);
}
return builder.build();
}
@ApiStatus.Experimental
public static final AnnotationSpec EXPERIMENTAL_API_ANNOTATION = AnnotationSpec.builder(ApiStatus.Experimental.class).build();
public static final AnnotationSpec NULL_MARKED = AnnotationSpec.builder(NullMarked.class).build();
public static final AnnotationSpec OVERRIDE = AnnotationSpec.builder(Override.class).build();
public static final AnnotationSpec GENERATED_FROM = AnnotationSpec.builder(GeneratedFrom.class)
.addMember("value", "$S", SharedConstants.getCurrentVersion().getId())
.build();
public static final Iterable<AnnotationSpec> CLASS_HEADER = List.of(
suppressWarnings("unused", "SpellCheckingInspection"),
NULL_MARKED,
GENERATED_FROM
);
private Annotations() {
}
}

View File

@ -0,0 +1,37 @@
package io.papermc.generator.utils;
import com.google.common.base.CaseFormat;
import com.google.common.collect.ImmutableMap;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Map;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.block.entity.BlockEntityType;
import org.jspecify.annotations.NullMarked;
@NullMarked
public final class BlockEntityMapping {
// if this become painful/too weird like the blockdata just rename the impl directly again
private static final Map<String, String> RENAMES = ImmutableMap.<String, String>builder()
.put("CraftFurnace", "CraftFurnaceFurnace")
.put("CraftMobSpawner", "CraftCreatureSpawner")
.put("CraftPiston", "CraftMovingPiston")
.put("CraftTrappedChest", "CraftChest") // not really a rename
.buildOrThrow();
public static final Map<ResourceKey<BlockEntityType<?>>, String> MAPPING;
static {
Map<ResourceKey<BlockEntityType<?>>, String> mapping = new IdentityHashMap<>();
BuiltInRegistries.BLOCK_ENTITY_TYPE.registryKeySet().forEach(key -> {
String name = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, key.location().getPath());
String implName = "Craft".concat(name);
mapping.put(key, RENAMES.getOrDefault(implName, implName));
});
MAPPING = Collections.unmodifiableMap(mapping);
}
}

View File

@ -0,0 +1,445 @@
package io.papermc.generator.utils;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Multimap;
import com.mojang.datafixers.util.Either;
import io.papermc.generator.types.craftblockdata.property.holder.VirtualField;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import net.minecraft.core.Direction;
import net.minecraft.core.FrontAndTop;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.util.StringRepresentable;
import net.minecraft.world.level.block.AbstractFurnaceBlock;
import net.minecraft.world.level.block.BigDripleafStemBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.CommandBlock;
import net.minecraft.world.level.block.IronBarsBlock;
import net.minecraft.world.level.block.MultifaceBlock;
import net.minecraft.world.level.block.NoteBlock;
import net.minecraft.world.level.block.PipeBlock;
import net.minecraft.world.level.block.StructureBlock;
import net.minecraft.world.level.block.TestBlock;
import net.minecraft.world.level.block.TestInstanceBlock;
import net.minecraft.world.level.block.entity.trialspawner.TrialSpawnerState;
import net.minecraft.world.level.block.entity.vault.VaultState;
import net.minecraft.world.level.block.state.properties.AttachFace;
import net.minecraft.world.level.block.state.properties.BambooLeaves;
import net.minecraft.world.level.block.state.properties.BedPart;
import net.minecraft.world.level.block.state.properties.BellAttachType;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.ChestType;
import net.minecraft.world.level.block.state.properties.ComparatorMode;
import net.minecraft.world.level.block.state.properties.CreakingHeartState;
import net.minecraft.world.level.block.state.properties.DoorHingeSide;
import net.minecraft.world.level.block.state.properties.DoubleBlockHalf;
import net.minecraft.world.level.block.state.properties.DripstoneThickness;
import net.minecraft.world.level.block.state.properties.Half;
import net.minecraft.world.level.block.state.properties.NoteBlockInstrument;
import net.minecraft.world.level.block.state.properties.PistonType;
import net.minecraft.world.level.block.state.properties.Property;
import net.minecraft.world.level.block.state.properties.RailShape;
import net.minecraft.world.level.block.state.properties.RedstoneSide;
import net.minecraft.world.level.block.state.properties.SculkSensorPhase;
import net.minecraft.world.level.block.state.properties.SlabType;
import net.minecraft.world.level.block.state.properties.StairsShape;
import net.minecraft.world.level.block.state.properties.StructureMode;
import net.minecraft.world.level.block.state.properties.TestBlockMode;
import net.minecraft.world.level.block.state.properties.Tilt;
import net.minecraft.world.level.block.state.properties.WallSide;
import org.bukkit.Axis;
import org.bukkit.Instrument;
import org.bukkit.block.BlockFace;
import org.bukkit.block.Orientation;
import org.bukkit.block.data.Ageable;
import org.bukkit.block.data.AnaloguePowerable;
import org.bukkit.block.data.Bisected;
import org.bukkit.block.data.Brushable;
import org.bukkit.block.data.Directional;
import org.bukkit.block.data.FaceAttachable;
import org.bukkit.block.data.Hangable;
import org.bukkit.block.data.Hatchable;
import org.bukkit.block.data.Levelled;
import org.bukkit.block.data.Lightable;
import org.bukkit.block.data.MultipleFacing;
import org.bukkit.block.data.Openable;
import org.bukkit.block.data.Orientable;
import org.bukkit.block.data.Powerable;
import org.bukkit.block.data.Rail;
import org.bukkit.block.data.Rotatable;
import org.bukkit.block.data.Segmentable;
import org.bukkit.block.data.Snowable;
import org.bukkit.block.data.Waterlogged;
import org.bukkit.block.data.type.Bamboo;
import org.bukkit.block.data.type.Bed;
import org.bukkit.block.data.type.Bell;
import org.bukkit.block.data.type.BigDripleaf;
import org.bukkit.block.data.type.Chest;
import org.bukkit.block.data.type.Comparator;
import org.bukkit.block.data.type.CreakingHeart;
import org.bukkit.block.data.type.Door;
import org.bukkit.block.data.type.Dripleaf;
import org.bukkit.block.data.type.Fence;
import org.bukkit.block.data.type.Furnace;
import org.bukkit.block.data.type.PointedDripstone;
import org.bukkit.block.data.type.RedstoneRail;
import org.bukkit.block.data.type.RedstoneWire;
import org.bukkit.block.data.type.ResinClump;
import org.bukkit.block.data.type.SculkSensor;
import org.bukkit.block.data.type.Slab;
import org.bukkit.block.data.type.Stairs;
import org.bukkit.block.data.type.Switch;
import org.bukkit.block.data.type.TechnicalPiston;
import org.bukkit.block.data.type.TrialSpawner;
import org.bukkit.block.data.type.Vault;
import org.bukkit.block.data.type.Wall;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public final class BlockStateMapping {
public record BlockData(String implName, @Nullable Class<? extends org.bukkit.block.data.BlockData> api,
Collection<? extends Property<?>> properties, Map<Property<?>, Field> propertyFields,
Multimap<Either<Field, VirtualField>, Property<?>> complexPropertyFields) {
}
private static final Map<String, String> API_RENAMES = ImmutableMap.<String, String>builder()
.put("SnowLayer", "Snow")
.put("StainedGlassPane", "GlassPane") // weird that this one implements glass pane but not the regular glass pane
.put("CeilingHangingSign", "HangingSign")
.put("RedStoneWire", "RedstoneWire")
.put("TripWire", "Tripwire")
.put("TripWireHook", "TripwireHook")
.put("Tnt", "TNT")
.put("BambooStalk", "Bamboo")
.put("Farm", "Farmland")
.put("ChiseledBookShelf", "ChiseledBookshelf")
.put("UntintedParticleLeaves", "Leaves")
.put("TintedParticleLeaves", "Leaves")
.put("StandingSign", "Sign")
.put("FenceGate", "Gate")
.buildOrThrow();
private static final Set<Class<? extends Block>> BLOCK_SUFFIX_INTENDED = Set.of(
CommandBlock.class,
StructureBlock.class,
NoteBlock.class,
TestBlock.class,
TestInstanceBlock.class
);
// virtual data that doesn't exist as constant in the source but still organized this way in the api
public static final ImmutableMultimap<Class<?>, VirtualField> VIRTUAL_NODES = ImmutableMultimap.<Class<?>, VirtualField>builder()
.build();
public static final Map<Property<?>, Field> FALLBACK_GENERIC_FIELDS;
static {
Map<Property<?>, Field> fallbackGenericFields = new HashMap<>();
fetchProperties(BlockStateProperties.class, (field, property) -> fallbackGenericFields.put(property, field), null);
FALLBACK_GENERIC_FIELDS = Collections.unmodifiableMap(fallbackGenericFields);
}
public static final Map<Class<? extends Block>, BlockData> MAPPING;
static {
Map<Class<? extends Block>, Collection<Property<?>>> specialBlocks = new IdentityHashMap<>();
for (Block block : BuiltInRegistries.BLOCK) {
if (!block.getStateDefinition().getProperties().isEmpty()) {
specialBlocks.put(block.getClass(), block.getStateDefinition().getProperties());
}
}
Map<Class<? extends Block>, BlockData> map = new IdentityHashMap<>();
for (Map.Entry<Class<? extends Block>, Collection<Property<?>>> entry : specialBlocks.entrySet()) {
Class<? extends Block> specialBlock = entry.getKey();
Collection<Property<?>> properties = new ArrayList<>(entry.getValue());
Map<Property<?>, Field> propertyFields = new HashMap<>(properties.size());
Multimap<Either<Field, VirtualField>, Property<?>> complexPropertyFields = ArrayListMultimap.create();
fetchProperties(specialBlock, (field, property) -> {
if (properties.contains(property)) {
propertyFields.put(property, field);
}
}, (field, property) -> {
if (properties.remove(property)) { // handle those separately and only count if the property was in the state definition
complexPropertyFields.put(Either.left(field), property);
}
});
// virtual nodes
if (VIRTUAL_NODES.containsKey(specialBlock)) {
for (VirtualField virtualField : VIRTUAL_NODES.get(specialBlock)) {
for (Property<?> property : virtualField.values()) {
if (properties.remove(property)) {
complexPropertyFields.put(Either.right(virtualField), property);
} else {
throw new IllegalStateException("Unhandled virtual node " + virtualField.name() + " for " + property + " in " + specialBlock.getCanonicalName());
}
}
}
}
String apiName = formatApiName(specialBlock);
String implName = "Craft".concat(apiName); // before renames
apiName = Formatting.stripWordOfCamelCaseName(apiName, "Base", true);
apiName = API_RENAMES.getOrDefault(apiName, apiName);
Class<? extends org.bukkit.block.data.BlockData> api = ClassHelper.classOr("org.bukkit.block.data.type." + apiName, null);
if (api == null) {
Class<?> directParent = specialBlock.getSuperclass();
if (specialBlocks.containsKey(directParent)) {
// if the properties are the same then always consider the parent
// check deeper in the tree?
if (specialBlocks.get(directParent).equals(entry.getValue())) {
String parentApiName = formatApiName(directParent);
parentApiName = Formatting.stripWordOfCamelCaseName(parentApiName, "Base", true);
parentApiName = API_RENAMES.getOrDefault(parentApiName, parentApiName);
api = ClassHelper.classOr("org.bukkit.block.data.type." + parentApiName, api);
}
}
}
if (api == null) { // todo remove this part
if (AbstractFurnaceBlock.class.isAssignableFrom(specialBlock)) {
api = Furnace.class; // for smoker and blast furnace
} else if (specialBlock == BigDripleafStemBlock.class) {
api = Dripleaf.class;
} else if (specialBlock == IronBarsBlock.class) {
api = Fence.class; // for glass pane (regular) and iron bars
} else if (specialBlock == MultifaceBlock.class) {
api = ResinClump.class;
}
}
map.put(specialBlock, new BlockData(implName, api, properties, propertyFields, complexPropertyFields));
}
MAPPING = Collections.unmodifiableMap(map);
}
private static final Map<String, Class<? extends org.bukkit.block.data.BlockData>> NAME_TO_DATA = Map.of(
BlockStateProperties.AGE_1.getName(), Ageable.class,
BlockStateProperties.LEVEL.getName(), Levelled.class
);
private static final Map<Property<?>, Class<? extends org.bukkit.block.data.BlockData>> PROPERTY_TO_DATA = ImmutableMap.<Property<?>, Class<? extends org.bukkit.block.data.BlockData>>builder()
// levelled and ageable are done using the property name
.put(BlockStateProperties.POWER, AnaloguePowerable.class)
.put(BlockStateProperties.HALF, Bisected.class)
.put(BlockStateProperties.DOUBLE_BLOCK_HALF, Bisected.class)
.put(BlockStateProperties.DUSTED, Brushable.class)
.put(BlockStateProperties.FACING, Directional.class)
.put(BlockStateProperties.HORIZONTAL_FACING, Directional.class)
.put(BlockStateProperties.ATTACH_FACE, FaceAttachable.class)
.put(BlockStateProperties.HANGING, Hangable.class)
.put(BlockStateProperties.HATCH, Hatchable.class)
.put(BlockStateProperties.LIT, Lightable.class)
// multiple facing is done by matching two or more pipe block properties
.put(BlockStateProperties.OPEN, Openable.class)
.put(BlockStateProperties.HORIZONTAL_AXIS, Orientable.class)
.put(BlockStateProperties.AXIS, Orientable.class)
.put(BlockStateProperties.POWERED, Powerable.class)
.put(BlockStateProperties.RAIL_SHAPE, Rail.class)
.put(BlockStateProperties.RAIL_SHAPE_STRAIGHT, Rail.class)
.put(BlockStateProperties.ROTATION_16, Rotatable.class)
.put(BlockStateProperties.SNOWY, Snowable.class)
.put(BlockStateProperties.WATERLOGGED, Waterlogged.class)
.put(BlockStateProperties.SEGMENT_AMOUNT, Segmentable.class)
.buildOrThrow();
private static final Map<Property<?>, Class<? extends org.bukkit.block.data.BlockData>> MAIN_PROPERTY_TO_DATA = Map.of(
BlockStateProperties.PISTON_TYPE, TechnicalPiston.class,
BlockStateProperties.STAIRS_SHAPE, Stairs.class
);
public static final Map<Class<? extends Enum<? extends StringRepresentable>>, Class<? extends Enum<?>>> ENUM_BRIDGE = ImmutableMap.<Class<? extends Enum<? extends StringRepresentable>>, Class<? extends Enum<?>>>builder()
.put(DoorHingeSide.class, Door.Hinge.class)
.put(SlabType.class, Slab.Type.class)
.put(StructureMode.class, org.bukkit.block.data.type.StructureBlock.Mode.class)
.put(DripstoneThickness.class, PointedDripstone.Thickness.class)
.put(WallSide.class, Wall.Height.class)
.put(BellAttachType.class, Bell.Attachment.class)
.put(NoteBlockInstrument.class, Instrument.class)
.put(StairsShape.class, Stairs.Shape.class)
.put(Direction.class, BlockFace.class)
.put(ComparatorMode.class, Comparator.Mode.class)
.put(PistonType.class, TechnicalPiston.Type.class)
.put(BedPart.class, Bed.Part.class)
.put(Half.class, Bisected.Half.class)
.put(AttachFace.class, FaceAttachable.AttachedFace.class)
.put(RailShape.class, Rail.Shape.class)
.put(SculkSensorPhase.class, SculkSensor.Phase.class)
.put(DoubleBlockHalf.class, Bisected.Half.class)
.put(Tilt.class, BigDripleaf.Tilt.class)
.put(ChestType.class, Chest.Type.class)
.put(RedstoneSide.class, RedstoneWire.Connection.class)
.put(Direction.Axis.class, Axis.class)
.put(BambooLeaves.class, Bamboo.Leaves.class)
.put(TrialSpawnerState.class, TrialSpawner.State.class)
.put(FrontAndTop.class, Orientation.class)
.put(VaultState.class, Vault.State.class)
.put(CreakingHeartState.class, CreakingHeart.State.class)
.put(TestBlockMode.class, org.bukkit.block.data.type.TestBlock.Mode.class)
.buildOrThrow();
public static @Nullable Class<? extends org.bukkit.block.data.BlockData> getBestSuitedApiClass(Class<?> block) {
if (!MAPPING.containsKey(block)) {
return null;
}
return getBestSuitedApiClass(MAPPING.get(block));
}
public static @Nullable Class<? extends org.bukkit.block.data.BlockData> getBestSuitedApiClass(BlockData data) {
if (data.api() != null) {
return data.api();
}
int pipeProps = 0;
Set<Class<? extends org.bukkit.block.data.BlockData>> extensions = new LinkedHashSet<>();
for (Property<?> property : data.properties()) {
if (MAIN_PROPERTY_TO_DATA.containsKey(property)) {
return MAIN_PROPERTY_TO_DATA.get(property);
}
if (NAME_TO_DATA.containsKey(property.getName())) {
extensions.add(NAME_TO_DATA.get(property.getName()));
continue;
}
if (PROPERTY_TO_DATA.containsKey(property)) {
extensions.add(PROPERTY_TO_DATA.get(property));
continue;
}
if (PipeBlock.PROPERTY_BY_DIRECTION.containsValue(property)) {
pipeProps++;
}
}
if (!extensions.isEmpty()) {
if (isExactly(extensions, Switch.class)) {
return Switch.class;
}
if (isExactly(extensions, RedstoneRail.class)) {
return RedstoneRail.class;
}
return extensions.iterator().next();
}
for (Property<?> property : data.complexPropertyFields().values()) {
if (PipeBlock.PROPERTY_BY_DIRECTION.containsValue(property)) {
pipeProps++;
}
}
if (pipeProps >= 2) {
return MultipleFacing.class;
}
return null;
}
private static boolean isExactly(Set<Class<? extends org.bukkit.block.data.BlockData>> extensions, Class<? extends org.bukkit.block.data.BlockData> globClass) {
return extensions.equals(ClassHelper.getAllInterfaces(globClass, org.bukkit.block.data.BlockData.class, new HashSet<>()));
}
private static String formatApiName(Class<?> specialBlock) {
String apiName = specialBlock.getSimpleName();
if (!BLOCK_SUFFIX_INTENDED.contains(specialBlock)) {
return apiName.substring(0, apiName.length() - "Block".length());
}
return apiName;
}
private static boolean handleComplexType(Field field, BiConsumer<Field, Property<?>> complexCallback) throws IllegalAccessException {
if (field.getType().isArray() && Property.class.isAssignableFrom(field.getType().getComponentType())) {
if (!field.trySetAccessible()) {
return true;
}
for (Property<?> property : (Property<?>[]) field.get(null)) {
complexCallback.accept(field, property);
}
return true;
}
if (Iterable.class.isAssignableFrom(field.getType()) && field.getGenericType() instanceof ParameterizedType complexType) {
Type[] args = complexType.getActualTypeArguments();
if (args.length == 1 && Property.class.isAssignableFrom(ClassHelper.eraseType(args[0]))) {
if (!field.trySetAccessible()) {
return true;
}
for (Property<?> property : (Iterable<Property<?>>) field.get(null)) {
complexCallback.accept(field, property);
}
}
return true;
}
if (Map.class.isAssignableFrom(field.getType()) && field.getGenericType() instanceof ParameterizedType complexType) {
if (!field.trySetAccessible()) {
return true;
}
Type[] args = complexType.getActualTypeArguments();
if (args.length == 2 && Property.class.isAssignableFrom(ClassHelper.eraseType(args[1]))) {
for (Property<?> property : ((Map<?, Property<?>>) field.get(null)).values()) {
complexCallback.accept(field, property);
}
return true;
}
}
return false;
}
private static void fetchProperties(Class<?> block, BiConsumer<Field, Property<?>> simpleCallback, @Nullable BiConsumer<Field, Property<?>> complexCallback) {
try {
for (Field field : block.getDeclaredFields()) {
if (ClassHelper.isStaticConstant(field, 0)) {
if (complexCallback != null && handleComplexType(field, complexCallback)) {
continue;
}
if (!Property.class.isAssignableFrom(field.getType())) {
continue;
}
if (field.trySetAccessible()) {
Property<?> property = ((Property<?>) field.get(null));
simpleCallback.accept(field, property);
}
}
}
} catch (ReflectiveOperationException ex) {
throw new RuntimeException(ex);
}
if (block.isInterface()) {
return;
}
// look deeper
if (block.getSuperclass() != null && block.getSuperclass() != Block.class) {
fetchProperties(block.getSuperclass(), simpleCallback, complexCallback);
}
for (Class<?> ext : block.getInterfaces()) {
fetchProperties(ext, simpleCallback, complexCallback);
}
}
}

View File

@ -0,0 +1,72 @@
package io.papermc.generator.utils;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Set;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public final class ClassHelper {
public static Set<Class<?>> getAllInterfaces(Class<?> clazz, Class<?> ignored, Set<Class<?>> interfaces) {
Class<?>[] classes = clazz.getInterfaces();
interfaces.addAll(Arrays.asList(classes));
for (Class<?> farClass : classes) {
if (farClass == ignored) {
continue;
}
getAllInterfaces(farClass, ignored, interfaces);
}
interfaces.remove(ignored);
return interfaces;
}
public static @Nullable Type getNestedTypeParameter(Type type, @Nullable Class<?>... classes) {
for (Class<?> clazz : classes) {
if (!(type instanceof ParameterizedType complexType)) {
return null;
}
Type[] types = complexType.getActualTypeArguments();
if (types.length != 1) {
return null;
}
if (clazz == null || eraseType(types[0]) == clazz) {
type = types[0];
}
}
return type;
}
public static Class<?> eraseType(Type type) {
if (type instanceof Class<?> clazz) {
return clazz;
}
if (type instanceof ParameterizedType complexType) {
return eraseType(complexType.getRawType());
}
throw new UnsupportedOperationException("Don't know how to turn " + type + " into its erased type!");
}
public static boolean isStaticConstant(Field field, int extraFlags) {
int flags = extraFlags | Modifier.STATIC | Modifier.FINAL;
return (field.getModifiers() & flags) == flags;
}
public static <T> @Nullable Class<? extends T> classOr(String className, @Nullable Class<? extends T> defaultClass) {
try {
return (Class<? extends T>) Class.forName(className);
} catch (ClassNotFoundException ignored) {
return defaultClass;
}
}
private ClassHelper() {
}
}

View File

@ -0,0 +1,10 @@
package io.papermc.generator.utils;
import org.jspecify.annotations.NullMarked;
@NullMarked
public final class CommonVariable {
public static final String INDEX = "index";
public static final String MAP_ENTRY = "entry";
}

View File

@ -0,0 +1,107 @@
package io.papermc.generator.utils;
import java.util.Optional;
import org.apache.commons.lang3.math.NumberUtils;
import java.util.Comparator;
import java.util.Locale;
import java.util.OptionalInt;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.IntStream;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import org.jspecify.annotations.NullMarked;
@NullMarked
public final class Formatting {
private static final Pattern ILLEGAL_FIELD_CHARACTERS = Pattern.compile("[.-/]");
public static String formatKeyAsField(String path) {
return ILLEGAL_FIELD_CHARACTERS.matcher(path.toUpperCase(Locale.ENGLISH)).replaceAll("_");
}
public static String formatTagFieldPrefix(String name, ResourceKey<? extends Registry<?>> registryKey) {
if (registryKey == Registries.BLOCK) {
return "";
}
if (registryKey == Registries.GAME_EVENT) {
return "GAME_EVENT_"; // Paper doesn't follow the format (should be GAME_EVENTS_) (pre 1.21)
}
return name.toUpperCase(Locale.ENGLISH) + "_";
}
public static Optional<String> formatTagKey(String tagDir, String resourcePath) {
int tagsIndex = resourcePath.indexOf(tagDir);
int dotIndex = resourcePath.lastIndexOf('.');
if (tagsIndex == -1 || dotIndex == -1) {
return Optional.empty();
}
return Optional.of(resourcePath.substring(tagsIndex + tagDir.length() + 1, dotIndex)); // namespace/tags/registry_key/[tag_key].json
}
public static String quoted(String value) {
return "\"" + value + "\"";
}
public static String[] asCode(int... values) {
return IntStream.of(values).mapToObj(Integer::toString).toArray(String[]::new);
}
public static String stripWordOfCamelCaseName(String name, String word, boolean onlyOnce) {
String newName = name;
int startIndex = 0;
while (true) {
int baseIndex = newName.indexOf(word, startIndex);
if (baseIndex == -1) {
return newName;
}
if ((baseIndex > 0 && !Character.isLowerCase(newName.charAt(baseIndex - 1))) ||
(baseIndex + word.length() < newName.length() && !Character.isUpperCase(newName.charAt(baseIndex + word.length())))) {
startIndex = baseIndex + word.length();
continue;
}
newName = newName.substring(0, baseIndex) + newName.substring(baseIndex + word.length());
startIndex = baseIndex;
if (onlyOnce) {
break;
}
}
return newName;
}
public static final Comparator<String> ALPHABETIC_KEY_ORDER = alphabeticKeyOrder(path -> path);
public static <T> Comparator<T> alphabeticKeyOrder(Function<T, String> mapper) {
return (o1, o2) -> {
String path1 = mapper.apply(o1);
String path2 = mapper.apply(o2);
OptionalInt trailingInt1 = tryParseTrailingInt(path1);
OptionalInt trailingInt2 = tryParseTrailingInt(path2);
if (trailingInt1.isPresent() && trailingInt2.isPresent()) {
return Integer.compare(trailingInt1.getAsInt(), trailingInt2.getAsInt());
}
return path1.compareTo(path2);
};
}
private static OptionalInt tryParseTrailingInt(String path) {
int delimiterIndex = path.lastIndexOf('_');
if (delimiterIndex != -1) {
String score = path.substring(delimiterIndex + 1);
if (NumberUtils.isDigits(score)) {
return OptionalInt.of(Integer.parseInt(score));
}
}
return OptionalInt.empty();
}
private Formatting() {
}
}

View File

@ -0,0 +1,44 @@
package io.papermc.generator.utils;
import org.jspecify.annotations.NullMarked;
@NullMarked
public final class Javadocs {
public static String getVersionDependentClassHeader(String objectIdentifier, String headerIdentifier) {
return """
Vanilla %s for %s.
@apiNote The fields provided here are a direct representation of
what is available from the vanilla game source. They may be
changed (including removals) on any Minecraft version
bump, so cross-version compatibility is not provided on the
same level as it is on most of the other API.
""".formatted(objectIdentifier, headerIdentifier);
}
public static String getVersionDependentField(String headerIdentifier) {
return """
%s
@apiNote This field is version-dependant and may be removed in future Minecraft versions
""".formatted(headerIdentifier);
}
public static final String CREATE_TYPED_KEY_JAVADOC = """
Creates a typed key for {@link $T} in the registry {@code $L}.
@param key the value's key in the registry
@return a new typed key
""";
public static final String CREATED_TAG_KEY_JAVADOC = """
Creates a tag key for {@link $T} in the registry {@code $L}.
@param key the tag key's key
@return a new tag key
""";
private Javadocs() {
}
}

View File

@ -0,0 +1,141 @@
package io.papermc.generator.utils;
import com.google.common.base.CaseFormat;
import java.util.Optional;
import java.util.function.Predicate;
import javax.lang.model.SourceVersion;
import org.jetbrains.annotations.Contract;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public class NamingManager {
private final @Nullable AccessKeyword accessKeyword;
private final String baseName;
private final String lowerCamelName, upperCamelName;
public NamingManager(NamingManager.@Nullable AccessKeyword accessKeyword, CaseFormat format, String baseName) {
this.accessKeyword = accessKeyword; // this is a little bit too restrictive for extra data hmm
this.baseName = baseName;
this.upperCamelName = format.to(CaseFormat.UPPER_CAMEL, baseName);
this.lowerCamelName = format.to(CaseFormat.LOWER_CAMEL, baseName);
}
public String getVariableName() {
return this.lowerCamelName;
}
public String getMethodBaseName() {
return this.upperCamelName;
}
public NameWrapper getMethodNameWrapper() {
return NameWrapper.wrap("get", this.upperCamelName);
}
public NameWrapper getVariableNameWrapper() {
return NameWrapper.wrap("", this.lowerCamelName);
}
public NameWrapper getterName(Predicate<String> keywordPredicate) {
return accessName(keywordPredicate, AccessKeyword::get, "get");
}
public NameWrapper setterName(Predicate<String> keywordPredicate) {
return accessName(keywordPredicate, AccessKeyword::set, "set");
}
public String simpleGetterName(Predicate<String> keywordPredicate) {
return getterName(keywordPredicate).concat();
}
public String simpleSetterName(Predicate<String> keywordPredicate) {
return setterName(keywordPredicate).concat();
}
private NameWrapper accessName(Predicate<String> keywordPredicate, KeywordFetcher keywordFetcher, String fallbackKeyword) {
final String name;
String accessKeyword;
if (keywordPredicate.test(this.baseName)) {
accessKeyword = Optional.ofNullable(this.accessKeyword).flatMap(keywordFetcher::fetch).orElse(fallbackKeyword);
name = this.upperCamelName;
} else {
accessKeyword = "";
name = this.lowerCamelName;
}
return NameWrapper.wrap(accessKeyword, name);
}
public String paramName(Class<?> type) {
final String paramName;
if (type.isPrimitive()) {
paramName = this.lowerCamelName;
} else {
paramName = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, type.getSimpleName());
}
return ensureValidName(paramName);
}
public static String ensureValidName(String name) {
if (!SourceVersion.isIdentifier(name) || SourceVersion.isKeyword(name)) {
return "_" + name;
}
return name;
}
public static class NameWrapper {
private final String keyword;
private final String name;
private String preValue = "", postValue = "";
private NameWrapper(String keyword, String name) {
this.keyword = keyword;
this.name = name;
}
public static NameWrapper wrap(String keyword, String name) {
return new NameWrapper(keyword, name);
}
@Contract(value = "_ -> this", mutates = "this")
public NameWrapper pre(String value) {
this.preValue = value;
return this;
}
@Contract(value = "_ -> this", mutates = "this")
public NameWrapper post(String value) {
this.postValue = value;
return this;
}
public String concat() {
String finalName = this.keyword + this.preValue + this.name + this.postValue;
this.preValue = this.postValue = ""; // reset
return ensureValidName(finalName);
}
}
@FunctionalInterface
private interface KeywordFetcher {
Optional<String> fetch(AccessKeyword accessKeyword);
}
public static AccessKeyword keywordGet(String keyword) {
return new AccessKeyword(Optional.of(keyword), Optional.empty());
}
public static AccessKeyword keywordSet(String keyword) {
return new AccessKeyword(Optional.empty(), Optional.of(keyword));
}
public static AccessKeyword keywordGetSet(String getter, String setter) {
return new AccessKeyword(Optional.of(getter), Optional.of(setter));
}
public record AccessKeyword(Optional<String> get, Optional<String> set) {
}
}

View File

@ -0,0 +1,27 @@
package io.papermc.generator.utils.experimental;
import com.mojang.serialization.Lifecycle;
import io.papermc.generator.Main;
import java.util.Set;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderGetter;
import net.minecraft.core.Registry;
import net.minecraft.data.worldgen.BootstrapContext;
import net.minecraft.resources.ResourceKey;
import org.jspecify.annotations.NullMarked;
@NullMarked
public record CollectingContext<T>(Set<ResourceKey<T>> registered,
Registry<T> registry) implements BootstrapContext<T> {
@Override
public Holder.Reference<T> register(ResourceKey<T> key, T value, Lifecycle lifecycle) {
this.registered.add(key);
return Holder.Reference.createStandAlone(this.registry, key);
}
@Override
public <S> HolderGetter<S> lookup(ResourceKey<? extends Registry<? extends S>> key) {
return Main.REGISTRY_ACCESS.lookupOrThrow(key);
}
}

View File

@ -0,0 +1,132 @@
package io.papermc.generator.utils.experimental;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.mojang.logging.LogUtils;
import io.papermc.generator.Main;
import io.papermc.generator.utils.Formatting;
import java.util.Collection;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.RegistrySetBuilder;
import net.minecraft.core.registries.Registries;
import net.minecraft.data.registries.TradeRebalanceRegistries;
import net.minecraft.data.registries.VanillaRegistries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.packs.PackResources;
import net.minecraft.server.packs.PackType;
import net.minecraft.server.packs.repository.BuiltInPackSource;
import net.minecraft.server.packs.resources.MultiPackResourceManager;
import net.minecraft.tags.TagKey;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
@NullMarked
public final class ExperimentalCollector {
private static final Logger LOGGER = LogUtils.getLogger();
private static final Map<ResourceKey<? extends Registry<?>>, RegistrySetBuilder.RegistryBootstrap<?>> VANILLA_REGISTRY_ENTRIES = VanillaRegistries.BUILDER.entries.stream()
.collect(Collectors.toMap(RegistrySetBuilder.RegistryStub::key, RegistrySetBuilder.RegistryStub::bootstrap));
private static final Map<RegistrySetBuilder, SingleFlagHolder> EXPERIMENTAL_REGISTRY_FLAGS = Map.of(
// Update for Experimental API
TradeRebalanceRegistries.BUILDER, FlagHolders.TRADE_REBALANCE
);
private static final Multimap<ResourceKey<? extends Registry<?>>, Map.Entry<SingleFlagHolder, RegistrySetBuilder.RegistryBootstrap<?>>> EXPERIMENTAL_REGISTRY_ENTRIES;
static {
EXPERIMENTAL_REGISTRY_ENTRIES = HashMultimap.create();
for (Map.Entry<RegistrySetBuilder, SingleFlagHolder> entry : EXPERIMENTAL_REGISTRY_FLAGS.entrySet()) {
for (RegistrySetBuilder.RegistryStub<?> stub : entry.getKey().entries) {
EXPERIMENTAL_REGISTRY_ENTRIES.put(stub.key(), Map.entry(entry.getValue(), stub.bootstrap()));
}
}
}
@SuppressWarnings("unchecked")
public static <T> Map<ResourceKey<T>, SingleFlagHolder> collectDataDrivenElementIds(Registry<T> registry) {
Collection<Map.Entry<SingleFlagHolder, RegistrySetBuilder.RegistryBootstrap<?>>> experimentalEntries = EXPERIMENTAL_REGISTRY_ENTRIES.get(registry.key());
if (experimentalEntries.isEmpty()) {
return Collections.emptyMap();
}
Map<ResourceKey<T>, SingleFlagHolder> result = new IdentityHashMap<>();
for (Map.Entry<SingleFlagHolder, RegistrySetBuilder.RegistryBootstrap<?>> experimentalEntry : experimentalEntries) {
RegistrySetBuilder.RegistryBootstrap<T> experimentalBootstrap = (RegistrySetBuilder.RegistryBootstrap<T>) experimentalEntry.getValue();
Set<ResourceKey<T>> experimental = Collections.newSetFromMap(new IdentityHashMap<>());
CollectingContext<T> experimentalCollector = new CollectingContext<>(experimental, registry);
experimentalBootstrap.run(experimentalCollector);
result.putAll(experimental.stream().collect(Collectors.toMap(key -> key, key -> experimentalEntry.getKey())));
}
RegistrySetBuilder.@Nullable RegistryBootstrap<T> vanillaBootstrap = (RegistrySetBuilder.RegistryBootstrap<T>) VANILLA_REGISTRY_ENTRIES.get(registry.key());
if (vanillaBootstrap != null) {
Set<ResourceKey<T>> vanilla = Collections.newSetFromMap(new IdentityHashMap<>());
CollectingContext<T> vanillaCollector = new CollectingContext<>(vanilla, registry);
vanillaBootstrap.run(vanillaCollector);
result.keySet().removeAll(vanilla);
}
return result;
}
// collect all the tags by grabbing the json from the data-packs
// another (probably) way is to hook into the data generator like the typed keys generator
public static Map<TagKey<?>, String> collectTags(MultiPackResourceManager resourceManager) {
Map<TagKey<?>, String> result = new IdentityHashMap<>();
// collect all vanilla tags
Multimap<ResourceKey<? extends Registry<?>>, String> vanillaTags = HashMultimap.create();
PackResources vanillaPack = resourceManager.listPacks()
.filter(packResources -> packResources.packId().equals(BuiltInPackSource.VANILLA_ID))
.findFirst()
.orElseThrow();
collectTagsFromPack(vanillaPack, (entry, path) -> vanillaTags.put(entry.key(), path));
// then distinct with other data-pack tags to know for sure newly created tags and so experimental one
resourceManager.listPacks().forEach(pack -> {
String packId = pack.packId();
if (packId.equals(BuiltInPackSource.VANILLA_ID)) return;
collectTagsFromPack(pack, (entry, path) -> {
if (vanillaTags.get(entry.key()).contains(path)) {
return;
}
result.put(entry.value().listTagIds()
.filter(tagKey -> tagKey.location().getPath().equals(path))
.findFirst()
.orElseThrow(), packId);
});
});
return Collections.unmodifiableMap(result);
}
private static void collectTagsFromPack(PackResources pack, BiConsumer<RegistryAccess.RegistryEntry<?>, String> output) {
Set<String> namespaces = pack.getNamespaces(PackType.SERVER_DATA);
for (String namespace : namespaces) {
Main.REGISTRY_ACCESS.registries().forEach(entry -> {
// this is probably expensive but can't find another way around and data-pack loader has similar logic
// the issue is that registry key can have parent/key but tag key can also have parent/key so parsing become a mess
// without having at least one of the two values
String tagDir = Registries.tagsDirPath(entry.key());
pack.listResources(PackType.SERVER_DATA, namespace, tagDir, (id, supplier) -> {
Formatting.formatTagKey(tagDir, id.getPath()).ifPresentOrElse(path -> output.accept(entry, path), () -> {
LOGGER.warn("Unable to parse the path: {}/{}/{}.json in the data-pack {} into a tag key", namespace, tagDir, id.getPath(), pack.packId());
});
});
});
}
}
private ExperimentalCollector() {
}
}

View File

@ -0,0 +1,16 @@
package io.papermc.generator.utils.experimental;
import net.minecraft.world.flag.FeatureFlag;
import net.minecraft.world.flag.FeatureFlagSet;
import net.minecraft.world.flag.FeatureFlags;
import org.jspecify.annotations.NullMarked;
@NullMarked
public interface FlagHolder {
default FeatureFlagSet flagSet() {
return FeatureFlags.REGISTRY.subset(this.flag());
}
FeatureFlag flag();
}

View File

@ -0,0 +1,24 @@
package io.papermc.generator.utils.experimental;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import net.minecraft.Util;
import net.minecraft.world.flag.FeatureFlag;
import net.minecraft.world.flag.FeatureFlags;
import org.bukkit.MinecraftExperimental;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class FlagHolders {
public static final SingleFlagHolder TRADE_REBALANCE = SingleFlagHolder.fromValue(FeatureFlags.TRADE_REBALANCE);
public static final SingleFlagHolder REDSTONE_EXPERIMENTS = SingleFlagHolder.fromValue(FeatureFlags.REDSTONE_EXPERIMENTS);
public static final SingleFlagHolder MINECART_IMPROVEMENTS = SingleFlagHolder.fromValue(FeatureFlags.MINECART_IMPROVEMENTS);
static final Map<FeatureFlag, MinecraftExperimental.Requires> ANNOTATION_EQUIVALENT = Util.make(new HashMap<SingleFlagHolder, MinecraftExperimental.Requires>(), map -> {
map.put(TRADE_REBALANCE, MinecraftExperimental.Requires.TRADE_REBALANCE);
map.put(REDSTONE_EXPERIMENTS, MinecraftExperimental.Requires.REDSTONE_EXPERIMENTS);
map.put(MINECART_IMPROVEMENTS, MinecraftExperimental.Requires.MINECART_IMPROVEMENTS);
}).entrySet().stream().collect(Collectors.toMap(entry -> entry.getKey().flag(), Map.Entry::getValue));
}

View File

@ -0,0 +1,49 @@
package io.papermc.generator.utils.experimental;
import com.google.common.base.Preconditions;
import com.google.common.collect.HashBiMap;
import java.util.HashMap;
import java.util.Map;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.flag.FeatureFlag;
import net.minecraft.world.flag.FeatureFlagSet;
import net.minecraft.world.flag.FeatureFlags;
import org.bukkit.MinecraftExperimental;
import org.jspecify.annotations.NullMarked;
@NullMarked
public record SingleFlagHolder(FeatureFlag flag) implements FlagHolder { // todo support multiple flags?
private static final Map<String, FeatureFlag> FEATURE_FLAG_CACHE = new HashMap<>();
private static final Map<FeatureFlag, ResourceLocation> FEATURE_FLAG_NAME = HashBiMap.create(FeatureFlags.REGISTRY.names).inverse();
static SingleFlagHolder fromValue(FeatureFlag flag) {
return new SingleFlagHolder(flag);
}
public static SingleFlagHolder fromSet(FeatureFlagSet standaloneSet) {
Preconditions.checkArgument(Long.bitCount(standaloneSet.mask) == 1, "Flag set size must be equals to 1.");
for (FeatureFlag flag : FeatureFlags.REGISTRY.names.values()) {
if (standaloneSet.contains(flag)) {
return fromValue(flag);
}
}
throw new IllegalStateException();
}
public static SingleFlagHolder fromName(String name) {
return fromValue(FEATURE_FLAG_CACHE.computeIfAbsent(name, key -> {
return FeatureFlags.REGISTRY.names.get(ResourceLocation.withDefaultNamespace(key));
}));
}
public MinecraftExperimental.Requires asAnnotationMember() {
MinecraftExperimental.Requires annotationMember = FlagHolders.ANNOTATION_EQUIVALENT.get(this.flag);
if (annotationMember == null) {
throw new UnsupportedOperationException("Don't know that feature flag: " + FEATURE_FLAG_NAME.get(this.flag));
}
return annotationMember;
}
}

View File

@ -0,0 +1,74 @@
package io.papermc.generator;
import io.papermc.generator.utils.BlockStateMapping;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import io.papermc.generator.utils.ClassHelper;
import net.minecraft.SharedConstants;
import net.minecraft.server.Bootstrap;
import net.minecraft.world.level.block.ChiseledBookShelfBlock;
import net.minecraft.world.level.block.MossyCarpetBlock;
import net.minecraft.world.level.block.PipeBlock;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.EnumProperty;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
public class BlockStatePropertyTest {
private static Set<Class<? extends Comparable<?>>> ENUM_PROPERTY_VALUES;
@BeforeAll
public static void getAllProperties() {
// bootstrap
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
Bootstrap.validate();
// get all properties
Set<Class<? extends Comparable<?>>> enumPropertyValues = Collections.newSetFromMap(new IdentityHashMap<>());
try {
for (Field field : BlockStateProperties.class.getDeclaredFields()) {
if (ClassHelper.isStaticConstant(field, Modifier.PUBLIC)) {
if (!EnumProperty.class.isAssignableFrom(field.getType())) {
continue;
}
enumPropertyValues.add(((EnumProperty<?>) field.get(null)).getValueClass());
}
}
ENUM_PROPERTY_VALUES = Collections.unmodifiableSet(enumPropertyValues);
} catch (ReflectiveOperationException ex) {
throw new RuntimeException(ex);
}
}
@Test
public void testReferences() throws NoSuchFieldException, IllegalAccessException {
// if renamed should change DataPropertyWriter#FIELD_TO_BASE_NAME/FIELD_TO_BASE_NAME_SPECIFICS
MethodHandles.Lookup lookup = MethodHandles.lookup();
lookup.findStaticVarHandle(ChiseledBookShelfBlock.class, "SLOT_OCCUPIED_PROPERTIES", List.class);
lookup.findStaticVarHandle(PipeBlock.class, "PROPERTY_BY_DIRECTION", Map.class);
MethodHandles.privateLookupIn(MossyCarpetBlock.class, lookup).findStaticVarHandle(MossyCarpetBlock.class, "PROPERTY_BY_DIRECTION", Map.class);
}
@Test
public void testBridge() {
Set<String> missingApiEquivalents = new HashSet<>();
for (Class<? extends Comparable<?>> value : ENUM_PROPERTY_VALUES) {
if (!BlockStateMapping.ENUM_BRIDGE.containsKey(value)) {
missingApiEquivalents.add(value.getCanonicalName());
}
}
Assertions.assertTrue(missingApiEquivalents.isEmpty(), () -> "Missing some api equivalent in the block state mapping enum bridge (BlockStateMapping#ENUM_BRIDGE) : " + String.join(", ", missingApiEquivalents));
}
}

View File

@ -0,0 +1,35 @@
package io.papermc.generator;
import io.github.classgraph.ClassGraph;
import io.github.classgraph.ScanResult;
import io.papermc.generator.types.goal.MobGoalNames;
import java.util.ArrayList;
import java.util.List;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.Mob;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class MobGoalConverterTest {
@Test
public void testBukkitMap() {
final List<Class<Mob>> classes;
try (ScanResult scanResult = new ClassGraph().enableClassInfo().whitelistPackages(Entity.class.getPackageName()).scan()) {
classes = scanResult.getSubclasses(Mob.class.getName()).loadClasses(Mob.class);
}
assertFalse(classes.isEmpty(), "There are supposed to be more than 0 mob classes!");
List<String> missingClasses = new ArrayList<>();
for (Class<Mob> nmsClass : classes) {
if (!MobGoalNames.bukkitMap.containsKey(nmsClass)) {
missingClasses.add(nmsClass.getCanonicalName());
}
}
assertTrue(missingClasses.isEmpty(), () -> "Missing some entity classes in the bukkit map: " + String.join(", ", missingClasses));
}
}

View File

@ -0,0 +1,50 @@
package io.papermc.generator;
import io.papermc.generator.registry.RegistryEntries;
import io.papermc.generator.registry.RegistryEntry;
import java.util.HashSet;
import java.util.Set;
import net.minecraft.SharedConstants;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.Bootstrap;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
public class RegistryMigrationTest {
@BeforeAll
public static void setup() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
Bootstrap.validate();
}
@Test
public void testBuiltInToDataDriven() {
Set<String> migratedRegistries = new HashSet<>();
for (RegistryEntry<?> entry : RegistryEntries.BUILT_IN) {
ResourceKey<? extends Registry<?>> key = entry.registryKey();
if (!BuiltInRegistries.REGISTRY.containsKey(key.location())) {
migratedRegistries.add(key.toString());
}
}
Assertions.assertTrue(migratedRegistries.isEmpty(), () -> "Some registries have become data-driven: %s".formatted(String.join(", ", migratedRegistries)));
}
@Test
public void testDataDrivenToBuiltIn() { // shouldn't really happen but just in case
Set<String> migratedRegistries = new HashSet<>();
for (RegistryEntry<?> entry : RegistryEntries.DATA_DRIVEN) {
ResourceKey<? extends Registry<?>> key = entry.registryKey();
if (BuiltInRegistries.REGISTRY.containsKey(key.location())) {
migratedRegistries.add(key.toString());
}
}
Assertions.assertTrue(migratedRegistries.isEmpty(), () -> "Some registries have become built-in: %s".formatted(String.join(", ", migratedRegistries)));
}
}

View File

@ -0,0 +1,10 @@
public net/minecraft/world/level/material/MapColor MATERIAL_COLORS
public net/minecraft/world/level/block/state/properties/IntegerProperty min
# for auto-marking experimental stuff
public net/minecraft/core/RegistrySetBuilder entries
public net/minecraft/core/RegistrySetBuilder$RegistryStub
public net/minecraft/data/registries/VanillaRegistries BUILDER
public net/minecraft/data/registries/TradeRebalanceRegistries BUILDER
public net/minecraft/world/flag/FeatureFlagRegistry names
public net/minecraft/world/flag/FeatureFlagSet mask