diff --git a/api/src/main/java/net/kyori/adventure/text/BuildableComponent.java b/api/src/main/java/net/kyori/adventure/text/BuildableComponent.java new file mode 100644 index 000000000..d1bd3c239 --- /dev/null +++ b/api/src/main/java/net/kyori/adventure/text/BuildableComponent.java @@ -0,0 +1,47 @@ +/* + * This file is part of adventure, licensed under the MIT License. + * + * Copyright (c) 2017-2025 KyoriPowered + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package net.kyori.adventure.text; + +import net.kyori.adventure.util.Buildable; +import org.jetbrains.annotations.ApiStatus; + +/** + * A component which may be built. + * + * @param the component type + * @param the builder type + * @since 4.0.0 + * @deprecated For removal in 6.0, since 4.26 with no replacement. See instead {@link Component#toBuilder()}. + */ +@Deprecated(forRemoval = true) +@ApiStatus.ScheduledForRemoval(inVersion = "6.0.0") +public sealed interface BuildableComponent> extends Buildable, Component permits KeybindComponent, NBTComponent, ObjectComponent, ScoreComponent, SelectorComponent, TextComponent, TranslatableComponent { + /** + * Create a builder from this component. + * + * @return the builder + */ + @Override + B toBuilder(); +} diff --git a/api/src/main/java/net/kyori/adventure/text/Component.java b/api/src/main/java/net/kyori/adventure/text/Component.java index 4d1d648e4..59796f858 100644 --- a/api/src/main/java/net/kyori/adventure/text/Component.java +++ b/api/src/main/java/net/kyori/adventure/text/Component.java @@ -108,7 +108,8 @@ * @see LinearComponents * @since 4.0.0 */ -public sealed interface Component extends ComponentBuilderApplicable, ComponentLike, HoverEventSource, StyleGetter, StyleSetter permits NBTComponent, ScopedComponent { +@SuppressWarnings("removal") +public sealed interface Component extends ComponentBuilderApplicable, ComponentLike, HoverEventSource, StyleGetter, StyleSetter permits BuildableComponent, ScopedComponent { /** * A predicate that checks equality of two {@code Component}s using {@link Objects#equals(Object, Object)}. * diff --git a/api/src/main/java/net/kyori/adventure/text/KeybindComponent.java b/api/src/main/java/net/kyori/adventure/text/KeybindComponent.java index 0e802eb28..a842dcda7 100644 --- a/api/src/main/java/net/kyori/adventure/text/KeybindComponent.java +++ b/api/src/main/java/net/kyori/adventure/text/KeybindComponent.java @@ -38,7 +38,8 @@ * @since 4.0.0 * @sinceMinecraft 1.12 */ -public sealed interface KeybindComponent extends ScopedComponent permits KeybindComponentImpl { +@SuppressWarnings("removal") +public sealed interface KeybindComponent extends ScopedComponent, BuildableComponent permits KeybindComponentImpl { /** * Gets the keybind. * diff --git a/api/src/main/java/net/kyori/adventure/text/NBTComponent.java b/api/src/main/java/net/kyori/adventure/text/NBTComponent.java index a41b24324..ac4480c48 100644 --- a/api/src/main/java/net/kyori/adventure/text/NBTComponent.java +++ b/api/src/main/java/net/kyori/adventure/text/NBTComponent.java @@ -49,7 +49,8 @@ * @since 4.0.0 * @sinceMinecraft 1.14 */ -public sealed interface NBTComponent> extends Component permits BlockNBTComponent, EntityNBTComponent, StorageNBTComponent { +@SuppressWarnings("removal") +public sealed interface NBTComponent> extends BuildableComponent> permits BlockNBTComponent, EntityNBTComponent, StorageNBTComponent { /** * The default value for {@link #interpret()}. * diff --git a/api/src/main/java/net/kyori/adventure/text/ObjectComponent.java b/api/src/main/java/net/kyori/adventure/text/ObjectComponent.java index 8db769606..0fe43741b 100644 --- a/api/src/main/java/net/kyori/adventure/text/ObjectComponent.java +++ b/api/src/main/java/net/kyori/adventure/text/ObjectComponent.java @@ -33,7 +33,8 @@ * @since 4.25.0 * @sinceMinecraft 1.21.9 */ -public sealed interface ObjectComponent extends ScopedComponent, ObjectContentsLike permits ObjectComponentImpl { +@SuppressWarnings("removal") +public sealed interface ObjectComponent extends ScopedComponent, BuildableComponent, ObjectContentsLike permits ObjectComponentImpl { /** * Gets the contents of this object component. * diff --git a/api/src/main/java/net/kyori/adventure/text/ScoreComponent.java b/api/src/main/java/net/kyori/adventure/text/ScoreComponent.java index f02ba016d..b5e2477e1 100644 --- a/api/src/main/java/net/kyori/adventure/text/ScoreComponent.java +++ b/api/src/main/java/net/kyori/adventure/text/ScoreComponent.java @@ -49,7 +49,8 @@ * * @since 4.0.0 */ -public sealed interface ScoreComponent extends ScopedComponent permits ScoreComponentImpl { +@SuppressWarnings("removal") +public sealed interface ScoreComponent extends ScopedComponent, BuildableComponent permits ScoreComponentImpl { /** * Gets the score name. * diff --git a/api/src/main/java/net/kyori/adventure/text/SelectorComponent.java b/api/src/main/java/net/kyori/adventure/text/SelectorComponent.java index 72944efcb..e5813f2c2 100644 --- a/api/src/main/java/net/kyori/adventure/text/SelectorComponent.java +++ b/api/src/main/java/net/kyori/adventure/text/SelectorComponent.java @@ -41,7 +41,8 @@ * * @since 4.0.0 */ -public sealed interface SelectorComponent extends ScopedComponent permits SelectorComponentImpl { +@SuppressWarnings("removal") +public sealed interface SelectorComponent extends ScopedComponent, BuildableComponent permits SelectorComponentImpl { /** * Gets the selector pattern. * diff --git a/api/src/main/java/net/kyori/adventure/text/TextComponent.java b/api/src/main/java/net/kyori/adventure/text/TextComponent.java index 691a9a6ba..bccc6e621 100644 --- a/api/src/main/java/net/kyori/adventure/text/TextComponent.java +++ b/api/src/main/java/net/kyori/adventure/text/TextComponent.java @@ -36,7 +36,8 @@ * * @since 4.0.0 */ -public sealed interface TextComponent extends ScopedComponent permits TextComponentImpl, VirtualComponent { +@SuppressWarnings("removal") +public sealed interface TextComponent extends ScopedComponent, BuildableComponent permits TextComponentImpl, VirtualComponent { /** * Gets the plain text content. * diff --git a/api/src/main/java/net/kyori/adventure/text/TranslatableComponent.java b/api/src/main/java/net/kyori/adventure/text/TranslatableComponent.java index bac76dd63..945f626da 100644 --- a/api/src/main/java/net/kyori/adventure/text/TranslatableComponent.java +++ b/api/src/main/java/net/kyori/adventure/text/TranslatableComponent.java @@ -58,7 +58,8 @@ * @see TranslationStore * @since 4.0.0 */ -public sealed interface TranslatableComponent extends ScopedComponent permits TranslatableComponentImpl { +@SuppressWarnings("removal") +public sealed interface TranslatableComponent extends ScopedComponent, BuildableComponent permits TranslatableComponentImpl { /** * Gets the translation key. * diff --git a/build-logic/src/main/kotlin/adventure.legacy-component-builder-abi-fix.gradle.kts b/build-logic/src/main/kotlin/adventure.legacy-component-builder-abi-fix.gradle.kts index 018b01e2f..b4ec454ea 100644 --- a/build-logic/src/main/kotlin/adventure.legacy-component-builder-abi-fix.gradle.kts +++ b/build-logic/src/main/kotlin/adventure.legacy-component-builder-abi-fix.gradle.kts @@ -12,8 +12,9 @@ import org.objectweb.asm.MethodVisitor import org.objectweb.asm.Opcodes // Remove in 6.x. -// In 5.x, ComponentBuilder#build erased from Object to Component, causing NoSuchMethodError for 4.x-compiled clients. -// This adds a build-time classfile bridge for build():Object while keeping the 5.x source API strongly typed. +// In 5.x, ComponentBuilder#build erased from Object/BuildableComponent to Component, +// causing NoSuchMethodError for 4.x-compiled clients. +// This adds build-time classfile bridges for the legacy return types while keeping the 5.x source API strongly typed. abstract class PatchComponentBuilderAbi : DefaultTask() { @get:InputFile @@ -28,11 +29,13 @@ abstract class PatchComponentBuilderAbi : DefaultTask() { val writer = ClassWriter(reader, 0) var hasComponentBuild = false + var hasBuildableComponentBuild = false var hasObjectBuild = false reader.accept(object : ClassVisitor(Opcodes.ASM9, writer) { override fun visitMethod(access: Int, name: String, desc: String, sig: String?, ex: Array?): MethodVisitor { if (name == "build" && desc == "()Lnet/kyori/adventure/text/Component;") hasComponentBuild = true + if (name == "build" && desc == "()Lnet/kyori/adventure/text/BuildableComponent;") hasBuildableComponentBuild = true if (name == "build" && desc == "()Ljava/lang/Object;") hasObjectBuild = true return super.visitMethod(access, name, desc, sig, ex) } @@ -40,6 +43,30 @@ abstract class PatchComponentBuilderAbi : DefaultTask() { override fun visitEnd() { check(hasComponentBuild) { "Missing ComponentBuilder.build():Component" } + if (!hasBuildableComponentBuild) { + super.visitMethod( + Opcodes.ACC_PUBLIC or Opcodes.ACC_BRIDGE or Opcodes.ACC_SYNTHETIC, + "build", + "()Lnet/kyori/adventure/text/BuildableComponent;", + null, + null + ).apply { + visitCode() + visitVarInsn(Opcodes.ALOAD, 0) + visitMethodInsn( + Opcodes.INVOKEINTERFACE, + "net/kyori/adventure/text/ComponentBuilder", + "build", + "()Lnet/kyori/adventure/text/Component;", + true + ) + visitTypeInsn(Opcodes.CHECKCAST, "net/kyori/adventure/text/BuildableComponent") + visitInsn(Opcodes.ARETURN) + visitMaxs(1, 1) + visitEnd() + } + } + if (!hasObjectBuild) { super.visitMethod( Opcodes.ACC_PUBLIC or Opcodes.ACC_BRIDGE or Opcodes.ACC_SYNTHETIC,