Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bukkit: wip native delegating facets #116

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -45,42 +45,44 @@
final class BukkitAudience extends FacetAudience<CommandSender> {
static final ThreadLocal<Plugin> PLUGIN = new ThreadLocal<>();
private static final Function<Player, UserConnection> VIA = new BukkitFacet.ViaHook();
private static final Collection<Facet.Chat<? extends CommandSender, ?>> CHAT = Facet.of(
private static final Collection<Facet.Chat<?, ?>> CHAT = Facet.of(
() -> new ViaFacet.Chat<>(Player.class, VIA),
// () -> new SpigotFacet.ChatWithType(),
// () -> new SpigotFacet.Chat(),
() -> new PaperFacet.Chat(),
() -> new CraftBukkitFacet.Chat(),
() -> new BukkitFacet.Chat());
private static final Collection<Facet.ActionBar<Player, ?>> ACTION_BAR = Facet.of(
private static final Collection<Facet.ActionBar<?, ?>> ACTION_BAR = Facet.of(
() -> new ViaFacet.ActionBarTitle<>(Player.class, VIA),
() -> new ViaFacet.ActionBar<>(Player.class, VIA),
// () -> new SpigotFacet.ActionBar(),
() -> new PaperFacet.ActionBar(),
() -> new CraftBukkitFacet.ActionBar_1_17(),
() -> new CraftBukkitFacet.ActionBar(),
() -> new CraftBukkitFacet.ActionBarLegacy());
private static final Collection<Facet.Title<Player, ?, ?, ?>> TITLE = Facet.of(
private static final Collection<Facet.Title<?, ?, ?, ?>> TITLE = Facet.of(
() -> new ViaFacet.Title<>(Player.class, VIA),
// () -> new PaperFacet.Title(),
() -> new PaperFacet.Title(),
() -> new CraftBukkitFacet.Title_1_17(),
() -> new CraftBukkitFacet.Title());
private static final Collection<Facet.Sound<Player, Vector>> SOUND = Facet.of(
private static final Collection<Facet.Sound<?, Vector>> SOUND = Facet.of(
() -> new PaperFacet.Sound(),
() -> new BukkitFacet.SoundWithCategory(),
() -> new BukkitFacet.Sound());
private static final Collection<Facet.EntitySound<Player, Object>> ENTITY_SOUND = Facet.of(
private static final Collection<Facet.EntitySound<?, ?>> ENTITY_SOUND = Facet.of(
() -> new PaperFacet.EntitySound(),
() -> new CraftBukkitFacet.EntitySound()
);
private static final Collection<Facet.Book<Player, ?, ?>> BOOK = Facet.of(
// () -> new SpigotFacet.Book(),
private static final Collection<Facet.Book<?, ?, ?>> BOOK = Facet.of(
() -> new PaperFacet.Book(),
() -> new CraftBukkitFacet.BookPost1_13(),
() -> new CraftBukkitFacet.Book1_13(),
() -> new CraftBukkitFacet.BookPre1_13());
private static final Collection<Facet.BossBar.Builder<Player, ?>> BOSS_BAR = Facet.of(
private static final Collection<Facet.BossBar.Builder<?, ?>> BOSS_BAR = Facet.of(
() -> new ViaFacet.BossBar.Builder<>(Player.class, VIA),
() -> new ViaFacet.BossBar.Builder1_9_To_1_15<>(Player.class, VIA),
() -> new PaperFacet.BossBarBuilder(),
() -> new CraftBukkitFacet.BossBar.Builder(),
() -> new BukkitFacet.BossBarBuilder(),
() -> new CraftBukkitFacet.BossBarWither.Builder());
private static final Collection<Facet.TabList<Player, ?>> TAB_LIST = Facet.of(
private static final Collection<Facet.TabList<?, ?>> TAB_LIST = Facet.of(
() -> new ViaFacet.TabList<>(Player.class, VIA),
() -> new PaperFacet.TabList(),
() -> new CraftBukkitFacet.TabList(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package net.kyori.adventure.platform.bukkit;

import java.lang.invoke.MethodHandle;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import net.kyori.adventure.platform.facet.Knob;
import org.jetbrains.annotations.Nullable;

import static net.kyori.adventure.platform.facet.Knob.logError;

final class MappedBuilder {
private final Class<?> builtClass;
private final Class<?> builderClass;
private final MethodHandle builderFactory;
private final MethodHandle buildMethod;
private final Map<String, MethodHandle> builderMethods;

static @Nullable MappedBuilder mappedBuilder(final @Nullable Class<?> built, final @Nullable Class<?> builder, final String builderMethodName) {
if (built == null || builder == null) {
return null;
}

final MethodHandle builderMethod = MinecraftReflection.findStaticMethod(built, builderMethodName, builder);
final MethodHandle buildMethod = MinecraftReflection.findMethod(builder, "build", built);

if (builderMethod == null || buildMethod == null) return null;

return new MappedBuilder(built, builder, builderMethod, buildMethod, discoverBuilderMethods(builder));
}

private static Map<String, MethodHandle> discoverBuilderMethods(final Class<?> builderClass) {
final Map<String, MethodHandle> discoveredMethods = new HashMap<>();

for (final Method method : builderClass.getMethods()) {
final String name = method.getName();
final int mod = method.getModifiers();

if (!Modifier.isPublic(mod) || Modifier.isStatic(mod)) continue;
if (
!builderClass.isAssignableFrom(method.getReturnType())
&& !method.getReturnType().isAssignableFrom(builderClass)
) continue;
if (name.equals("build")) continue;

method.setAccessible(true);

try {
discoveredMethods.put(name, MinecraftReflection.lookup().unreflect(method));
} catch (final IllegalAccessException ex) {
logError(ex, "Could not unreflect builder method {}", method);
}
}

return Collections.unmodifiableMap(discoveredMethods);
}

public MappedBuilder(final Class<?> built, final Class<?> builder, final MethodHandle builderFactory, final MethodHandle build, final Map<String, MethodHandle> builderMethods) {
this.builtClass = built;
this.builderClass = builder;
this.builderFactory = builderFactory;
this.buildMethod = build;
this.builderMethods = builderMethods;
}

public Instance begin() {
try {
final Object builder = this.builderFactory.invoke();
return new Instance(builder);
} catch (final Throwable ex) {
logError(ex, "Failed to create builder instance for {}", this.builderClass);
return new Instance(null);
}
}

final class Instance {
private final Object builder;

Instance(final Object builder) {
this.builder = builder;
}

Instance set(final String key, final Object value) {
if (this.builder != null) {
final MethodHandle builderMethod = MappedBuilder.this.builderMethods.get(key);
if (builderMethod == null) {
Knob.logMessage("No builder method found in {} for {}", MappedBuilder.this.builderClass, key);
} else {
try {
builderMethod.invoke(this.builder, value);
} catch (final Throwable ex) {
logError(ex, "Failed to invoke builder method {}", builderMethod);
}
}
}

return this;
}

@Nullable Object build() {
if (this.builder == null) return null;

try {
return MappedBuilder.this.buildMethod.invoke(this.builder);
} catch (final Throwable ex) {
logError(ex, "Failed to create an instance of {}", MappedBuilder.this.builtClass);
return null;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@
import java.lang.invoke.MethodType;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import net.kyori.adventure.platform.facet.Knob;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
Expand Down Expand Up @@ -364,6 +366,40 @@ public static boolean hasMethod(final @Nullable Class<?> holderClass, final Stri
return null;
}

/**
* Gets a class field if possible and makes it accessible.
*
* @param holderClass a class
* @param fieldName a field name
* @return a field value
*/
public static @Nullable Object findConstantFieldValue(final @Nullable Class<?> holderClass, final @NotNull String... fieldName) {
return findField(holderClass, null, fieldName);
}

/**
* Gets the value of a constant field in a class
*
* @param holderClass a class
* @param expectedType the expected type of the field
* @param fieldNames a field name
* @return a field value
*/
@SuppressWarnings("unchecked")
public static <T> @Nullable T findConnstantFieldValue(final @Nullable Class<?> holderClass, final @Nullable Class<T> expectedType, final @NotNull String... fieldNames) {
final Field f = findField(holderClass, expectedType, fieldNames);
if (f == null) return null;
final int mod = f.getModifiers();
if (!Modifier.isStatic(mod) || !Modifier.isFinal(mod)) return null;

try {
return (T) f.get(null);
} catch (IllegalArgumentException | IllegalAccessException ex) {
Knob.logError(ex, "While getting value of field {}.{}", f.getDeclaringClass().getName(), f.getName());
return null;
}
}

/**
* Return a method handle that can set the value of the provided field.
*
Expand Down
Loading