From 27880479547e1bf54b4f21efadc55c4ce83fe5c0 Mon Sep 17 00:00:00 2001 From: Kieran Wallbanks Date: Thu, 26 Aug 2021 18:22:49 +0100 Subject: [PATCH] feat(api): Pointers supplier --- .../adventure/pointer/PointersSupplier.java | 127 +++++++++++++++ .../pointer/PointersSupplierImpl.java | 144 ++++++++++++++++++ .../kyori/adventure/pointer/PointersTest.java | 25 +++ 3 files changed, 296 insertions(+) create mode 100644 api/src/main/java/net/kyori/adventure/pointer/PointersSupplier.java create mode 100644 api/src/main/java/net/kyori/adventure/pointer/PointersSupplierImpl.java diff --git a/api/src/main/java/net/kyori/adventure/pointer/PointersSupplier.java b/api/src/main/java/net/kyori/adventure/pointer/PointersSupplier.java new file mode 100644 index 000000000..6471ba344 --- /dev/null +++ b/api/src/main/java/net/kyori/adventure/pointer/PointersSupplier.java @@ -0,0 +1,127 @@ +/* + * This file is part of adventure, licensed under the MIT License. + * + * Copyright (c) 2017-2021 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.pointer; + +import java.util.function.Function; +import net.kyori.adventure.builder.AbstractBuilder; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * A supplier of {@link Pointers} that allows for the implementation of pointers + * in a static context without having to manually create a new pointers instance for + * each instance of a given type. + * + *

An example of how this could be implemented is as follows:

+ *
{@code
+ * public class MyPointeredObject extends SomePointeredParent implements Pointered {
+ *   private static final PointersSupplier POINTERS = PointersSupplier.builder()
+ *     .parent(SomePointeredParent.POINTERS) // Fallback to the parent to get pointers from.
+ *     .resolving(Identity.UUID, MyPointeredObject::getUniqueId)
+ *     .resolving(Identity.DISPLAY_NAME, MyPointeredObject::getDisplayName)
+ *     .build();
+ *
+ *   @Override
+ *   public Pointers pointers() {
+ *     return POINTERS.view(this);
+ *   }
+ * }
+ * }
+ * + * @param the type + * @since 4.17.0 + */ +public interface PointersSupplier { + /** + * Gets a new pointers supplier builder. + * + * @param the type + * @return the builder + * @since 4.17.0 + */ + static @NotNull Builder builder() { + return new PointersSupplierImpl.BuilderImpl<>(); + } + + /** + * Creates a pointers view for the given instance. + * + * @param instance the instance + * @return the view + * @since 4.17.0 + */ + @NotNull Pointers view(final @NotNull T instance); + + /** + * Checks if this supplier supports a given pointer. + * + * @param pointer the pointer + * @param

the type of the pointer + * @return if this supplier supports a given pointer + * @since 4.17.0 + */ +

boolean supports(final @NotNull Pointer

pointer); + + /** + * Returns the resolver for a given pointer (if any). + * + * @param pointer the pointer + * @param

the type of the pointer + * @return the resolver, if any + * @since 4.17.0 + */ +

@Nullable Function resolver(final @NotNull Pointer

pointer); + + /** + * A builder for {@link PointersSupplier}. + * + * @param the type to supply pointers for + * @since 4.17.0 + */ + interface Builder extends AbstractBuilder> { + /** + * Sets (or removes, if {@code null}) the parent pointer supplier that will be used + * to resolve pointers that are not supplied by this supplier. + * + * @param parent the parent + * @return this builder + * @since 4.17.0 + */ + @Contract("_ -> this") + @NotNull Builder parent(final @Nullable PointersSupplier parent); + + /** + * Adds a resolver for a given pointer. + * + * @param pointer the pointer + * @param resolver the resolver + * @param

the type of the pointer + * @return this builder + * @since 4.17.0 + */ + @Contract("_, _ -> this") +

@NotNull Builder resolving(final @NotNull Pointer

pointer, final @NotNull Function resolver); + } +} diff --git a/api/src/main/java/net/kyori/adventure/pointer/PointersSupplierImpl.java b/api/src/main/java/net/kyori/adventure/pointer/PointersSupplierImpl.java new file mode 100644 index 000000000..77a1417b4 --- /dev/null +++ b/api/src/main/java/net/kyori/adventure/pointer/PointersSupplierImpl.java @@ -0,0 +1,144 @@ +/* + * This file is part of adventure, licensed under the MIT License. + * + * Copyright (c) 2017-2021 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.pointer; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; +import java.util.function.Supplier; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +final class PointersSupplierImpl implements PointersSupplier { + private final PointersSupplier parent; + private final Map, Function> resolvers; + + PointersSupplierImpl(final @NotNull BuilderImpl builder) { + this.parent = builder.parent; + this.resolvers = new HashMap<>(builder.resolvers); + } + + @Override + public @NotNull Pointers view(final @NotNull T instance) { + return new ForwardingPointers<>(instance, this); + } + + @Override + public

boolean supports(@NotNull Pointer

pointer) { + if (this.resolvers.containsKey(Objects.requireNonNull(pointer, "pointer"))) { + return true; + } else if (this.parent == null) { + return false; + } else { + return this.parent.supports(pointer); + } + } + + @Override + @SuppressWarnings("unchecked") // all values are checked on entry + public @Nullable

Function resolver(@NotNull Pointer

pointer) { + Function resolver = this.resolvers.get(Objects.requireNonNull(pointer, "pointer")); + + if (resolver != null) { + return (Function) resolver; + } else if (this.parent == null) { + return null; + } else { + return this.parent.resolver(pointer); + } + } + + static final class ForwardingPointers implements Pointers { + private final U instance; + private final PointersSupplierImpl supplier; + + ForwardingPointers(final @NotNull U instance, final @NotNull PointersSupplierImpl supplier) { + this.instance = instance; + this.supplier = supplier; + } + + @Override + @SuppressWarnings("unchecked") // all values are checked on entry + public @NotNull Optional get(final @NotNull Pointer pointer) { + Function resolver = this.supplier.resolvers.get(Objects.requireNonNull(pointer, "pointer")); + + // Fallback to the parent. + if (resolver == null) { + resolver = this.supplier.parent.resolver(pointer); + } + + // Finally, wrap in an optional. + if (resolver == null) { + return Optional.empty(); + } else { + return Optional.ofNullable((T) resolver.apply(this.instance)); + } + } + + @Override + public boolean supports(final @NotNull Pointer pointer) { + return this.supplier.supports(pointer); + } + + @Override + @SuppressWarnings("unchecked") // all values are checked on entry + public @NotNull Pointers.Builder toBuilder() { + final Pointers.Builder builder = this.supplier.parent == null ? Pointers.builder() : this.supplier.parent.view(this.instance).toBuilder(); + + for (final Map.Entry, Function> entry : this.supplier.resolvers.entrySet()) { + builder.withDynamic(entry.getKey(), (Supplier) () -> entry.getValue().apply(this.instance)); + } + + return builder; + } + } + + static final class BuilderImpl implements Builder { + private PointersSupplier parent = null; + private final Map, Function> resolvers; + + BuilderImpl() { + this.resolvers = new HashMap<>(); + } + + @Override + public @NotNull Builder parent(final @Nullable PointersSupplier parent) { + this.parent = parent; + return this; + } + + @Override + public @NotNull

Builder resolving(final @NotNull Pointer

pointer, final @NotNull Function resolver) { + this.resolvers.put(pointer, resolver); + return this; + } + + @Override + public @NotNull PointersSupplier build() { + return new PointersSupplierImpl<>(this); + } + } +} diff --git a/api/src/test/java/net/kyori/adventure/pointer/PointersTest.java b/api/src/test/java/net/kyori/adventure/pointer/PointersTest.java index fe151a4b3..e475046d7 100644 --- a/api/src/test/java/net/kyori/adventure/pointer/PointersTest.java +++ b/api/src/test/java/net/kyori/adventure/pointer/PointersTest.java @@ -65,4 +65,29 @@ public void ofPointers() { assertEquals("test", p2.getOrDefault(pointer, null)); assertEquals("tset", p2.getOrDefault(pointer, null)); // make sure the value does change } + + @Test + public void ofPointersSuppliers() { + final Pointer p0 = Pointer.pointer(String.class, Key.key("adventure:test1")); + final Pointer p1 = Pointer.pointer(String.class, Key.key("adventure:test2")); + + final PointersSupplier parent = PointersSupplier.builder() + .resolving(p0, (object) -> "0") + .build(); + final PointersSupplier child = PointersSupplier.builder() + .parent(parent) + .resolving(p1, (object) -> "1") + .build(); + + assertTrue(child.supports(p0)); + assertTrue(child.supports(p1)); + + final Pointers childPointers = child.view(10); + assertEquals("0", childPointers.get(p0).get()); + assertEquals("1", childPointers.get(p1).get()); + + final Pointers rebuilt = childPointers.toBuilder().withStatic(p0, "1").build(); + assertEquals("1", rebuilt.get(p0).get()); + assertEquals("1", rebuilt.get(p1).get()); + } }