From b6ba19f68aed045fb6f6309e6745b485750f8628 Mon Sep 17 00:00:00 2001 From: Riley Park Date: Sun, 25 Apr 2021 17:59:06 -0700 Subject: [PATCH] ComponentSerializer services --- .../net/kyori/adventure/util/Services.java | 64 +++++++++ .../net/kyori/adventure/util/Services0.java | 35 +++++ .../gson/GsonComponentSerializer.java | 27 ++++ .../gson/GsonComponentSerializerImpl.java | 11 +- .../legacy/LegacyComponentSerializer.java | 39 +++++- .../legacy/LegacyComponentSerializerImpl.java | 18 ++- .../plain/PlainComponentSerializer.java | 44 +++--- .../plain/PlainComponentSerializerImpl.java | 39 ++++-- .../plain/PlainTextComponentSerializer.java | 130 ++++++++++++++++++ .../PlainTextComponentSerializerImpl.java | 88 ++++++++++++ ... => PlainTextComponentSerializerTest.java} | 8 +- 11 files changed, 460 insertions(+), 43 deletions(-) create mode 100644 api/src/main/java/net/kyori/adventure/util/Services.java create mode 100644 api/src/main/java/net/kyori/adventure/util/Services0.java create mode 100644 text-serializer-plain/src/main/java/net/kyori/adventure/text/serializer/plain/PlainTextComponentSerializer.java create mode 100644 text-serializer-plain/src/main/java/net/kyori/adventure/text/serializer/plain/PlainTextComponentSerializerImpl.java rename text-serializer-plain/src/test/java/net/kyori/adventure/text/serializer/plain/{PlainComponentSerializerTest.java => PlainTextComponentSerializerTest.java} (88%) diff --git a/api/src/main/java/net/kyori/adventure/util/Services.java b/api/src/main/java/net/kyori/adventure/util/Services.java new file mode 100644 index 0000000000..cbfab07ed1 --- /dev/null +++ b/api/src/main/java/net/kyori/adventure/util/Services.java @@ -0,0 +1,64 @@ +/* + * 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.util; + +import java.util.Iterator; +import java.util.Optional; +import java.util.ServiceLoader; +import org.checkerframework.checker.nullness.qual.NonNull; + +/** + * Tools for working with {@link ServiceLoader}s. + * + * @since 4.8.0 + */ +public final class Services { + private Services() { + } + + /** + * Locates a service provider. + * + * @param type the service provider type + * @param

the service provider type + * @return a service + * @since 4.8.0 + */ + public static

Optional

service(final @NonNull Class

type) { + final ServiceLoader

loader = Services0.loader(type); + final Iterator

it = loader.iterator(); + while(it.hasNext()) { + try { + final P provider = it.next(); + if(it.hasNext()) { + throw new IllegalStateException("Expected to find one " + type + " service provider, found multiple"); + } + return Optional.of(provider); + } catch(final Throwable t) { + // ignored + } + } + return Optional.empty(); + } +} diff --git a/api/src/main/java/net/kyori/adventure/util/Services0.java b/api/src/main/java/net/kyori/adventure/util/Services0.java new file mode 100644 index 0000000000..20918e55e6 --- /dev/null +++ b/api/src/main/java/net/kyori/adventure/util/Services0.java @@ -0,0 +1,35 @@ +/* + * 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.util; + +import java.util.ServiceLoader; + +final class Services0 { + private Services0() { + } + + static ServiceLoader loader(final Class type) { + return ServiceLoader.load(type, type.getClassLoader()); + } +} diff --git a/text-serializer-gson/src/main/java/net/kyori/adventure/text/serializer/gson/GsonComponentSerializer.java b/text-serializer-gson/src/main/java/net/kyori/adventure/text/serializer/gson/GsonComponentSerializer.java index e8772c7dcf..e86e28f6f4 100644 --- a/text-serializer-gson/src/main/java/net/kyori/adventure/text/serializer/gson/GsonComponentSerializer.java +++ b/text-serializer-gson/src/main/java/net/kyori/adventure/text/serializer/gson/GsonComponentSerializer.java @@ -32,6 +32,7 @@ import net.kyori.adventure.util.Buildable; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import org.jetbrains.annotations.ApiStatus; /** * A gson component serializer. @@ -153,4 +154,30 @@ interface Builder extends Buildable.Builder { @Override @NonNull GsonComponentSerializer build(); } + + /** + * A {@link GsonComponentSerializer} service provider. + * + * @since 4.8.0 + */ + @ApiStatus.Internal + interface Provider { + /** + * Provides a standard {@link GsonComponentSerializer}. + * + * @return a {@link GsonComponentSerializer} + * @since 4.8.0 + */ + @ApiStatus.Internal + @NonNull GsonComponentSerializer gson(); + + /** + * Provides a legacy {@link GsonComponentSerializer}. + * + * @return a {@link GsonComponentSerializer} + * @since 4.8.0 + */ + @ApiStatus.Internal + @NonNull GsonComponentSerializer gsonLegacy(); + } } diff --git a/text-serializer-gson/src/main/java/net/kyori/adventure/text/serializer/gson/GsonComponentSerializerImpl.java b/text-serializer-gson/src/main/java/net/kyori/adventure/text/serializer/gson/GsonComponentSerializerImpl.java index 785d2f0c29..57f6fad4ef 100644 --- a/text-serializer-gson/src/main/java/net/kyori/adventure/text/serializer/gson/GsonComponentSerializerImpl.java +++ b/text-serializer-gson/src/main/java/net/kyori/adventure/text/serializer/gson/GsonComponentSerializerImpl.java @@ -26,6 +26,7 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonElement; +import java.util.Optional; import java.util.function.UnaryOperator; import net.kyori.adventure.key.Key; import net.kyori.adventure.text.BlockNBTComponent; @@ -35,12 +36,18 @@ import net.kyori.adventure.text.format.Style; import net.kyori.adventure.text.format.TextColor; import net.kyori.adventure.text.format.TextDecoration; +import net.kyori.adventure.util.Services; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; final class GsonComponentSerializerImpl implements GsonComponentSerializer { - static final GsonComponentSerializer INSTANCE = new GsonComponentSerializerImpl(false, null, false); - static final GsonComponentSerializer LEGACY_INSTANCE = new GsonComponentSerializerImpl(true, null, true); + private static final Optional SERVICE = Services.service(Provider.class); + static final GsonComponentSerializer INSTANCE = SERVICE + .map(Provider::gson) + .orElseGet(() -> new GsonComponentSerializerImpl(false, null, false)); + static final GsonComponentSerializer LEGACY_INSTANCE = SERVICE + .map(Provider::gsonLegacy) + .orElseGet(() -> new GsonComponentSerializerImpl(true, null, true)); private final Gson serializer; private final UnaryOperator populator; diff --git a/text-serializer-legacy/src/main/java/net/kyori/adventure/text/serializer/legacy/LegacyComponentSerializer.java b/text-serializer-legacy/src/main/java/net/kyori/adventure/text/serializer/legacy/LegacyComponentSerializer.java index 350fd0dc4c..77cfa1c7d7 100644 --- a/text-serializer-legacy/src/main/java/net/kyori/adventure/text/serializer/legacy/LegacyComponentSerializer.java +++ b/text-serializer-legacy/src/main/java/net/kyori/adventure/text/serializer/legacy/LegacyComponentSerializer.java @@ -23,17 +23,19 @@ */ package net.kyori.adventure.text.serializer.legacy; +import java.util.function.Consumer; import java.util.regex.Pattern; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.event.HoverEvent; -import net.kyori.adventure.text.format.Style; import net.kyori.adventure.text.flattener.ComponentFlattener; +import net.kyori.adventure.text.format.Style; import net.kyori.adventure.text.serializer.ComponentSerializer; import net.kyori.adventure.util.Buildable; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import org.jetbrains.annotations.ApiStatus; /** * A legacy component serializer. @@ -261,4 +263,39 @@ interface Builder extends Buildable.Builder { @Override @NonNull LegacyComponentSerializer build(); } + + /** + * A {@link LegacyComponentSerializer} service provider. + * + * @since 4.8.0 + */ + @ApiStatus.Internal + interface Provider { + /** + * Provides a {@link LegacyComponentSerializer} using {@link #AMPERSAND_CHAR}. + * + * @return a {@link LegacyComponentSerializer} + * @since 4.8.0 + */ + @ApiStatus.Internal + @NonNull LegacyComponentSerializer legacyAmpersand(); + + /** + * Provides a {@link LegacyComponentSerializer} using {@link #SECTION_CHAR}. + * + * @return a {@link LegacyComponentSerializer} + * @since 4.8.0 + */ + @ApiStatus.Internal + @NonNull LegacyComponentSerializer legacySection(); + + /** + * Completes the building process of {@link Builder}. + * + * @return a {@link Consumer} + * @since 4.8.0 + */ + @ApiStatus.Internal + @NonNull Consumer legacy(); + } } diff --git a/text-serializer-legacy/src/main/java/net/kyori/adventure/text/serializer/legacy/LegacyComponentSerializerImpl.java b/text-serializer-legacy/src/main/java/net/kyori/adventure/text/serializer/legacy/LegacyComponentSerializerImpl.java index 4017faf704..de9d68eaf7 100644 --- a/text-serializer-legacy/src/main/java/net/kyori/adventure/text/serializer/legacy/LegacyComponentSerializerImpl.java +++ b/text-serializer-legacy/src/main/java/net/kyori/adventure/text/serializer/legacy/LegacyComponentSerializerImpl.java @@ -30,7 +30,9 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; +import java.util.function.Consumer; import java.util.regex.Pattern; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextComponent; @@ -43,6 +45,7 @@ import net.kyori.adventure.text.format.TextFormat; import net.kyori.adventure.text.flattener.ComponentFlattener; import net.kyori.adventure.text.flattener.FlattenerListener; +import net.kyori.adventure.util.Services; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -95,8 +98,18 @@ final class LegacyComponentSerializerImpl implements LegacyComponentSerializer { } } - static final LegacyComponentSerializer SECTION_SERIALIZER = new LegacyComponentSerializerImpl(SECTION_CHAR, HEX_CHAR, null, false, false, ComponentFlattener.basic()); - static final LegacyComponentSerializer AMPERSAND_SERIALIZER = new LegacyComponentSerializerImpl(AMPERSAND_CHAR, HEX_CHAR, null, false, false, ComponentFlattener.basic()); + private static final Optional SERVICE = Services.service(Provider.class); + static final LegacyComponentSerializer SECTION_SERIALIZER = SERVICE + .map(Provider::legacySection) + .orElseGet(() -> new LegacyComponentSerializerImpl(SECTION_CHAR, HEX_CHAR, null, false, false, ComponentFlattener.basic())); + static final LegacyComponentSerializer AMPERSAND_SERIALIZER = SERVICE + .map(Provider::legacyAmpersand) + .orElseGet(() -> new LegacyComponentSerializerImpl(AMPERSAND_CHAR, HEX_CHAR, null, false, false, ComponentFlattener.basic())); + static final Consumer BUILDER = SERVICE + .map(Provider::legacy) + .orElseGet(() -> builder -> { + // NOOP + }); private final char character; private final char hexCharacter; @@ -471,6 +484,7 @@ static final class BuilderImpl implements Builder { private ComponentFlattener flattener = ComponentFlattener.basic(); BuilderImpl() { + BUILDER.accept(this); // let service provider touch the builder before anybody else touches it } BuilderImpl(final @NonNull LegacyComponentSerializerImpl serializer) { diff --git a/text-serializer-plain/src/main/java/net/kyori/adventure/text/serializer/plain/PlainComponentSerializer.java b/text-serializer-plain/src/main/java/net/kyori/adventure/text/serializer/plain/PlainComponentSerializer.java index e4e9a652ea..7c149cdd86 100644 --- a/text-serializer-plain/src/main/java/net/kyori/adventure/text/serializer/plain/PlainComponentSerializer.java +++ b/text-serializer-plain/src/main/java/net/kyori/adventure/text/serializer/plain/PlainComponentSerializer.java @@ -36,40 +36,42 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; -import static java.util.Objects.requireNonNull; - /** * A plain component serializer. * *

Plain does not support more complex features such as, but not limited * to, colours, decorations, {@link ClickEvent}, and {@link HoverEvent}.

* + * @deprecated for removal since 4.8.0, use {@link PlainTextComponentSerializer} instead * @since 4.0.0 */ +@Deprecated public class PlainComponentSerializer implements ComponentSerializer, Buildable { - private static final PlainComponentSerializer INSTANCE = builder().build(); - /** * A component serializer for plain-based serialization and deserialization. * * @return serializer instance + * @deprecated for removal since 4.8.0, use {@link PlainTextComponentSerializer#plainText()} instead * @since 4.0.0 */ + @Deprecated public static @NonNull PlainComponentSerializer plain() { - return INSTANCE; + return PlainComponentSerializerImpl.INSTANCE; } /** * Create a new builder. * * @return a new plain serializer builder + * @deprecated for removal since 4.8.0, use {@link PlainTextComponentSerializer#builder()} instead * @since 4.7.0 */ + @Deprecated public static PlainComponentSerializer.@NonNull Builder builder() { return new PlainComponentSerializerImpl.BuilderImpl(); } - private final ComponentFlattener flattener; + final PlainTextComponentSerializer serializer; /** * Constructs. @@ -79,7 +81,7 @@ public class PlainComponentSerializer implements ComponentSerializer keybind, final @Nullable Function translatable) { - final ComponentFlattener.Builder builder = ComponentFlattener.basic().toBuilder(); - if(keybind != null) builder.mapper(KeybindComponent.class, keybind); - if(translatable != null) builder.mapper(TranslatableComponent.class, translatable); - this.flattener = builder.build(); + this(PlainComponentSerializerImpl.newSerializerForLegacyFunctions(keybind, translatable)); } - PlainComponentSerializer(final @NonNull ComponentFlattener flattener) { - this.flattener = flattener; + PlainComponentSerializer(final @NonNull PlainTextComponentSerializer serializer) { + this.serializer = serializer; } @Override public @NonNull TextComponent deserialize(final @NonNull String input) { - return Component.text(input); + return this.serializer.deserialize(input); } @Override public @NonNull String serialize(final @NonNull Component component) { - final StringBuilder sb = new StringBuilder(); - this.serialize(sb, component); - return sb.toString(); + return this.serializer.serialize(component); } /** @@ -119,25 +116,27 @@ public PlainComponentSerializer(final @Nullable Function { - /** * Set the component flattener to use. * @@ -145,9 +144,10 @@ public interface Builder extends Buildable.Builder { * * @param flattener the new flattener * @return this builder + * @deprecated for removal since 4.8.0, use {@link PlainTextComponentSerializer.Builder#flattener(ComponentFlattener)} instead * @since 4.7.0 */ + @Deprecated @NonNull Builder flattener(final @NonNull ComponentFlattener flattener); - } } diff --git a/text-serializer-plain/src/main/java/net/kyori/adventure/text/serializer/plain/PlainComponentSerializerImpl.java b/text-serializer-plain/src/main/java/net/kyori/adventure/text/serializer/plain/PlainComponentSerializerImpl.java index b86bbf24c7..818bbc4638 100644 --- a/text-serializer-plain/src/main/java/net/kyori/adventure/text/serializer/plain/PlainComponentSerializerImpl.java +++ b/text-serializer-plain/src/main/java/net/kyori/adventure/text/serializer/plain/PlainComponentSerializerImpl.java @@ -23,37 +23,52 @@ */ package net.kyori.adventure.text.serializer.plain; +import java.util.function.Function; +import net.kyori.adventure.text.KeybindComponent; +import net.kyori.adventure.text.TranslatableComponent; import net.kyori.adventure.text.flattener.ComponentFlattener; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; -import static java.util.Objects.requireNonNull; - +@Deprecated final class PlainComponentSerializerImpl { + static final PlainComponentSerializer INSTANCE = new PlainComponentSerializer(); + + private PlainComponentSerializerImpl() { + } + + static PlainTextComponentSerializer newSerializerForLegacyFunctions( + final @Nullable Function keybind, + final @Nullable Function translatable + ) { + if(keybind == null && translatable == null) { + return PlainTextComponentSerializer.plainText(); + } + final ComponentFlattener.Builder builder = ComponentFlattener.basic().toBuilder(); + if(keybind != null) builder.mapper(KeybindComponent.class, keybind); + if(translatable != null) builder.mapper(TranslatableComponent.class, translatable); + return PlainTextComponentSerializer.builder().flattener(builder.build()).build(); + } static final class BuilderImpl implements PlainComponentSerializer.Builder { - private ComponentFlattener flattener; + private final PlainTextComponentSerializer.Builder builder = PlainTextComponentSerializer.builder(); BuilderImpl() { - this.flattener = ComponentFlattener.basic().toBuilder() - .unknownMapper(comp -> { - throw new UnsupportedOperationException("Don't know how to turn " + comp.getClass().getSimpleName() + " into a string"); - }) - .build(); } - BuilderImpl(final ComponentFlattener flattener) { - this.flattener = flattener; + BuilderImpl(final PlainComponentSerializer serializer) { + this.builder.flattener(((PlainTextComponentSerializerImpl) serializer.serializer).flattener); } @Override public PlainComponentSerializer.@NonNull Builder flattener(final @NonNull ComponentFlattener flattener) { - this.flattener = requireNonNull(flattener, "flattener"); + this.builder.flattener(flattener); return this; } @Override public @NonNull PlainComponentSerializer build() { - return new PlainComponentSerializer(this.flattener); + return new PlainComponentSerializer(this.builder.build()); } } } diff --git a/text-serializer-plain/src/main/java/net/kyori/adventure/text/serializer/plain/PlainTextComponentSerializer.java b/text-serializer-plain/src/main/java/net/kyori/adventure/text/serializer/plain/PlainTextComponentSerializer.java new file mode 100644 index 0000000000..695cc72cab --- /dev/null +++ b/text-serializer-plain/src/main/java/net/kyori/adventure/text/serializer/plain/PlainTextComponentSerializer.java @@ -0,0 +1,130 @@ +/* + * 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.text.serializer.plain; + +import java.util.function.Consumer; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.flattener.ComponentFlattener; +import net.kyori.adventure.text.serializer.ComponentSerializer; +import net.kyori.adventure.util.Buildable; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.jetbrains.annotations.ApiStatus; + +/** + * A plain-text component serializer. + * + *

Plain does not support more complex features such as, but not limited + * to, colours, decorations, {@link ClickEvent}, and {@link HoverEvent}.

+ * + * @since 4.8.0 + */ +public interface PlainTextComponentSerializer extends ComponentSerializer, Buildable { + /** + * A component serializer for plain-based serialization and deserialization. + * + * @return serializer instance + * @since 4.8.0 + */ + static @NonNull PlainTextComponentSerializer plainText() { + return PlainTextComponentSerializerImpl.INSTANCE; + } + + /** + * Create a new builder. + * + * @return a new plain serializer builder + * @since 4.8.0 + */ + static PlainTextComponentSerializer.@NonNull Builder builder() { + return new PlainTextComponentSerializerImpl.BuilderImpl(); + } + + @Override + default @NonNull TextComponent deserialize(final @NonNull String input) { + return Component.text(input); + } + + @Override + default @NonNull String serialize(final @NonNull Component component) { + final StringBuilder sb = new StringBuilder(); + this.serialize(sb, component); + return sb.toString(); + } + + /** + * Serializes. + * + * @param sb the string builder + * @param component the component + * @since 4.8.0 + */ + void serialize(final @NonNull StringBuilder sb, final @NonNull Component component); + + /** + * A builder for the plain-text component serializer. + * + * @since 4.8.0 + */ + interface Builder extends Buildable.Builder { + /** + * Set the component flattener to use. + * + *

The default flattener is {@link ComponentFlattener#basic()} modified to throw exceptions on unknown component types.

+ * + * @param flattener the new flattener + * @return this builder + * @since 4.8.0 + */ + @NonNull Builder flattener(final @NonNull ComponentFlattener flattener); + } + + /** + * A {@link PlainTextComponentSerializer} service provider. + * + * @since 4.8.0 + */ + @ApiStatus.Internal + interface Provider { + /** + * Provides a {@link PlainTextComponentSerializer}. + * + * @return a {@link PlainTextComponentSerializer} + * @since 4.8.0 + */ + @ApiStatus.Internal + @NonNull PlainTextComponentSerializer plainTextSimple(); + + /** + * Completes the building process of {@link Builder}. + * + * @return a {@link Consumer} + * @since 4.8.0 + */ + @ApiStatus.Internal + @NonNull Consumer plainText(); + } +} diff --git a/text-serializer-plain/src/main/java/net/kyori/adventure/text/serializer/plain/PlainTextComponentSerializerImpl.java b/text-serializer-plain/src/main/java/net/kyori/adventure/text/serializer/plain/PlainTextComponentSerializerImpl.java new file mode 100644 index 0000000000..3b317f291a --- /dev/null +++ b/text-serializer-plain/src/main/java/net/kyori/adventure/text/serializer/plain/PlainTextComponentSerializerImpl.java @@ -0,0 +1,88 @@ +/* + * 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.text.serializer.plain; + +import java.util.Optional; +import java.util.function.Consumer; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.flattener.ComponentFlattener; +import net.kyori.adventure.util.Services; +import org.checkerframework.checker.nullness.qual.NonNull; + +import static java.util.Objects.requireNonNull; + +final class PlainTextComponentSerializerImpl implements PlainTextComponentSerializer { + private static final ComponentFlattener DEFAULT_FLATTENER = ComponentFlattener.basic().toBuilder() + .unknownMapper(component -> { + throw new UnsupportedOperationException("Don't know how to turn " + component.getClass().getSimpleName() + " into a string"); + }) + .build(); + private static final Optional SERVICE = Services.service(Provider.class); + static final PlainTextComponentSerializer INSTANCE = SERVICE + .map(Provider::plainTextSimple) + .orElseGet(() -> new PlainTextComponentSerializerImpl(DEFAULT_FLATTENER)); + static final Consumer BUILDER = SERVICE + .map(Provider::plainText) + .orElseGet(() -> builder -> { + // NOOP + }); + final ComponentFlattener flattener; + + PlainTextComponentSerializerImpl(final ComponentFlattener flattener) { + this.flattener = flattener; + } + + @Override + public void serialize(final @NonNull StringBuilder sb, final @NonNull Component component) { + this.flattener.flatten(requireNonNull(component, "component"), sb::append); + } + + @Override + public @NonNull Builder toBuilder() { + return new BuilderImpl(this); + } + + static final class BuilderImpl implements PlainTextComponentSerializer.Builder { + private ComponentFlattener flattener = DEFAULT_FLATTENER; + + BuilderImpl() { + BUILDER.accept(this); + } + + BuilderImpl(final PlainTextComponentSerializerImpl serializer) { + this.flattener = serializer.flattener; + } + + @Override + public PlainTextComponentSerializer.@NonNull Builder flattener(final @NonNull ComponentFlattener flattener) { + this.flattener = requireNonNull(flattener, "flattener"); + return this; + } + + @Override + public @NonNull PlainTextComponentSerializer build() { + return new PlainTextComponentSerializerImpl(this.flattener); + } + } +} diff --git a/text-serializer-plain/src/test/java/net/kyori/adventure/text/serializer/plain/PlainComponentSerializerTest.java b/text-serializer-plain/src/test/java/net/kyori/adventure/text/serializer/plain/PlainTextComponentSerializerTest.java similarity index 88% rename from text-serializer-plain/src/test/java/net/kyori/adventure/text/serializer/plain/PlainComponentSerializerTest.java rename to text-serializer-plain/src/test/java/net/kyori/adventure/text/serializer/plain/PlainTextComponentSerializerTest.java index 49fb077e6c..a17a9d598a 100644 --- a/text-serializer-plain/src/test/java/net/kyori/adventure/text/serializer/plain/PlainComponentSerializerTest.java +++ b/text-serializer-plain/src/test/java/net/kyori/adventure/text/serializer/plain/PlainTextComponentSerializerTest.java @@ -31,10 +31,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -class PlainComponentSerializerTest { +class PlainTextComponentSerializerTest { @Test void testSimpleFrom() { - assertEquals(Component.text("foo"), PlainComponentSerializer.plain().deserialize("foo")); + assertEquals(Component.text("foo"), PlainTextComponentSerializer.plainText().deserialize("foo")); } @Test @@ -52,7 +52,7 @@ void testToLegacy() { ) .append(Component.text("baz")) .build(); - assertEquals("hifoobarbaz", PlainComponentSerializer.plain().serialize(c1)); + assertEquals("hifoobarbaz", PlainTextComponentSerializer.plainText().serialize(c1)); final TextComponent c2 = Component.text().content("Hello there, ") .decoration(TextDecoration.BOLD, TextDecoration.State.TRUE) @@ -66,6 +66,6 @@ void testToLegacy() { .color(NamedTextColor.BLUE) ) .build(); - assertEquals("Hello there, you!", PlainComponentSerializer.plain().serialize(c2)); + assertEquals("Hello there, you!", PlainTextComponentSerializer.plainText().serialize(c2)); } }