Skip to content

Commit

Permalink
fix(api): add missing ScopedComponent overrides
Browse files Browse the repository at this point in the history
Fixes GH-1154
  • Loading branch information
zml2008 committed Feb 9, 2025
1 parent 5256a9c commit 0da7909
Show file tree
Hide file tree
Showing 7 changed files with 238 additions and 29 deletions.
1 change: 1 addition & 0 deletions api/src/main/java/net/kyori/adventure/text/Component.java
Original file line number Diff line number Diff line change
Expand Up @@ -2559,6 +2559,7 @@ default boolean hasStyling() {
* @return the optimized component
* @since 4.9.0
*/
@ScopedComponentOverrideNotRequired
default @NotNull Component compact() {
return ComponentCompaction.compact(this, null);
}
Expand Down
69 changes: 69 additions & 0 deletions api/src/main/java/net/kyori/adventure/text/ScopedComponent.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,14 @@
package net.kyori.adventure.text;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEventSource;
import net.kyori.adventure.text.format.Style;
import net.kyori.adventure.text.format.StyleBuilderApplicable;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.util.ARGBLike;
Expand All @@ -42,6 +45,12 @@
* @since 4.0.0
*/
public interface ScopedComponent<C extends Component> extends Component {
@Override
@SuppressWarnings("unchecked")
default @NotNull C asComponent() {
return (C) Component.super.asComponent();
}

@Override
@NotNull C children(final @NotNull List<? extends ComponentLike> children);

Expand All @@ -60,6 +69,12 @@ public interface ScopedComponent<C extends Component> extends Component {
return (C) Component.super.style(style);
}

@Override
@SuppressWarnings("unchecked")
default @NotNull C style(final @NotNull Consumer<Style.Builder> consumer, final Style.Merge.@NotNull Strategy strategy) {
return (C) Component.super.style(consumer, strategy);
}

@Override
@SuppressWarnings("unchecked")
default @NotNull C mergeStyle(final @NotNull Component that) {
Expand Down Expand Up @@ -90,6 +105,30 @@ public interface ScopedComponent<C extends Component> extends Component {
return (C) Component.super.append(builder);
}

@Override
@SuppressWarnings("unchecked")
default @NotNull C appendNewline() {
return (C) Component.super.appendNewline();
}

@Override
@SuppressWarnings("unchecked")
default @NotNull C appendSpace() {
return (C) Component.super.appendSpace();
}

@Override
@SuppressWarnings("unchecked")
default @NotNull C applyFallbackStyle(final @NotNull StyleBuilderApplicable @NotNull ... style) {
return (C) Component.super.applyFallbackStyle(style);
}

@Override
@SuppressWarnings("unchecked")
default @NotNull C applyFallbackStyle(final @NotNull Style style) {
return (C) Component.super.applyFallbackStyle(style);
}

@Override
@SuppressWarnings("unchecked")
default @NotNull C mergeStyle(final @NotNull Component that, final @NotNull Set<Style.Merge> merges) {
Expand Down Expand Up @@ -138,6 +177,18 @@ public interface ScopedComponent<C extends Component> extends Component {
return (C) Component.super.decoration(decoration, state);
}

@Override
@SuppressWarnings("unchecked")
default @NotNull C decorationIfAbsent(final @NotNull TextDecoration decoration, final TextDecoration.@NotNull State state) {
return (C) Component.super.decorationIfAbsent(decoration, state);
}

@Override
@SuppressWarnings("unchecked")
default @NotNull C decorations(final @NotNull Map<TextDecoration, TextDecoration.State> decorations) {
return (C) Component.super.decorations(decorations);
}

@Override
@SuppressWarnings("unchecked")
default @NotNull C clickEvent(final @Nullable ClickEvent event) {
Expand All @@ -155,4 +206,22 @@ public interface ScopedComponent<C extends Component> extends Component {
default @NotNull C insertion(final @Nullable String insertion) {
return (C) Component.super.insertion(insertion);
}

@Override
@SuppressWarnings("unchecked")
default @NotNull C replaceText(final @NotNull Consumer<TextReplacementConfig.Builder> configurer) {
return (C) Component.super.replaceText(configurer);
}

@Override
@SuppressWarnings("unchecked")
default @NotNull C replaceText(final @NotNull TextReplacementConfig config) {
return (C) Component.super.replaceText(config);
}

@Override
@SuppressWarnings("unchecked")
default @NotNull C font(final @Nullable Key key) {
return (C) Component.super.font(key);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* 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 java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* A marker annotation for {@link Component}-returning methods in a supertype of {@link ScopedComponent} that do not need generified overrides.
*
* @since 4.19.0
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface ScopedComponentOverrideNotRequired {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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.audience;

import net.kyori.test.Overrides;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

class AudienceOverridesTest {
@ParameterizedTest
@ValueSource(classes = {
ForwardingAudience.class,
ForwardingAudience.Single.class
})
void ensureForwardingAudiencesOverrideRequiredMethods(final Class<?> audience) {
Overrides.failIfMissing(
Audience.class,
Overrides.methods(Audience.class, ForwardingAudienceOverrideNotRequired.class),
audience,
Overrides.methods(audience, ForwardingAudienceOverrideNotRequired.class)
);
}
}
Original file line number Diff line number Diff line change
@@ -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 java.util.Set;
import java.util.stream.Collectors;
import net.kyori.test.Overrides;
import org.junit.jupiter.api.Test;

public class ScopedComponentOverridesTest {
// check that all methods are overridden properly
@Test
void ensureScopedComponentOverridesComponentMethods() {
final Set<Overrides.MethodInfo> expected = Overrides.inheritedMethodStream(Component.class, ScopedComponentOverrideNotRequired.class)
.filter(it -> it.returnType.equals(Component.class))
.filter(it -> !it.isDeprecated)
.collect(Collectors.toSet());

Overrides.failIfMissing(
Component.class,
expected,
ScopedComponent.class,
Overrides.methods(ScopedComponent.class, ScopedComponentOverrideNotRequired.class)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,57 +21,66 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.kyori.adventure.audience;
package net.kyori.test;

import com.google.common.collect.Sets;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import java.util.stream.Stream;
import org.jetbrains.annotations.Nullable;

import static org.junit.jupiter.api.Assertions.fail;

class AudienceOverrides {
@ParameterizedTest
@ValueSource(classes = {
ForwardingAudience.class,
ForwardingAudience.Single.class
})
void ensureForwardingAudiencesOverrideRequiredMethods(final Class<?> audience) {
final Set<MethodInfo> missing = Sets.difference(methods(Audience.class), methods(audience));
public final class Overrides {
private Overrides() {
}

private static Stream<MethodInfo> methodStream(final Method[] methods, final @Nullable Class<? extends Annotation> exclusionAnno) {
return Arrays.stream(methods)
.filter(method -> exclusionAnno == null || !method.isAnnotationPresent(exclusionAnno)) // there are some that truly are default methods
.filter(method -> !Modifier.isStatic(method.getModifiers())) // unlikely to exist, but best we exclude them just in case
.map(MethodInfo::new);
}

public static Set<MethodInfo> methods(final Class<?> in, final Class<? extends Annotation> exclusionAnno) {
return methodStream(in.getDeclaredMethods(), exclusionAnno)
.collect(Collectors.toSet());
}

public static Stream<MethodInfo> inheritedMethodStream(final Class<?> in, final Class<? extends Annotation> exclusionAnno) {
return methodStream(in.getMethods(), exclusionAnno);
}

public static void failIfMissing(final Class<?> parent, final Set<MethodInfo> parentMethods, final Class<?> child, final Set<MethodInfo> childMethods) {
final Set<Overrides.MethodInfo> missing = Sets.difference(parentMethods, childMethods);
if (!missing.isEmpty()) {
final StringBuilder error = new StringBuilder();
error.append(audience.getSimpleName()).append(" is missing override for ").append(Audience.class.getSimpleName()).append(" methods:");
for (final MethodInfo method : missing) {
error.append(child.getSimpleName()).append(" is missing override for ").append(parent.getSimpleName()).append(" methods:");
for (final Overrides.MethodInfo method : missing) {
error.append('\n').append("- ").append(method);
}
error.append('\n');
fail(error.toString());
}
}

private static Set<MethodInfo> methods(final Class<?> in) {
return Arrays.stream(in.getDeclaredMethods())
.filter(method -> !method.isAnnotationPresent(ForwardingAudienceOverrideNotRequired.class)) // there are some that truly are default methods
.filter(method -> !Modifier.isStatic(method.getModifiers())) // unlikely to exist, but best we exclude them just in case
.map(MethodInfo::new)
.collect(Collectors.toSet());
}

// todo(kashike): this can be a record once we have a Java 16 source-set
static final class MethodInfo {
final String name;
final Class<?> returnType;
final Class<?>[] paramTypes;
public static final class MethodInfo {
public final String name;
public final Class<?> returnType;
public final Class<?>[] paramTypes;
public final boolean isDeprecated;

MethodInfo(final Method method) {
this.name = method.getName();
this.returnType = method.getReturnType();
this.paramTypes = method.getParameterTypes();
this.isDeprecated = method.isAnnotationPresent(Deprecated.class);
}

@Override
Expand Down
8 changes: 4 additions & 4 deletions build-logic/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget

plugins {
`kotlin-dsl`
}
Expand All @@ -24,10 +26,8 @@ java {

kotlin {
target {
compilations.configureEach {
kotlinOptions {
jvmTarget = "11"
}
compilerOptions {
jvmTarget = JvmTarget.JVM_11
}
}
}

0 comments on commit 0da7909

Please sign in to comment.