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 000000000..07c79b978
--- /dev/null
+++ b/api/src/main/java/net/kyori/adventure/util/Services.java
@@ -0,0 +1,72 @@
+/*
+ * 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 {
+ // net.kyori.adventure.serviceLoadFailuresAreFatal
+ private static final boolean SERVICE_LOAD_FAILURES_ARE_FATAL = Boolean.parseBoolean(System.getProperty(String.join(".", "net", "kyori", "adventure", "serviceLoadFailuresAreFatal"), String.valueOf(true)));
+
+ private Services() {
+ }
+
+ /**
+ * Locates a service.
+ *
+ * @param type the service type
+ * @param
the service type
+ * @return a service, or {@link Optional#empty()}
+ * @since 4.8.0
+ */
+ public static
@NonNull Optional
service(final @NonNull Class
type) {
+ final ServiceLoader
loader = Services0.loader(type);
+ final Iterator
it = loader.iterator();
+ while(it.hasNext()) {
+ final P instance;
+ try {
+ instance = it.next();
+ } catch(final Throwable t) {
+ if(SERVICE_LOAD_FAILURES_ARE_FATAL) {
+ throw new IllegalStateException("Encountered an exception loading service " + type, t);
+ } else {
+ continue;
+ }
+ }
+ if(it.hasNext()) {
+ throw new IllegalStateException("Expected to find one service " + type + ", found multiple");
+ }
+ return Optional.of(instance);
+ }
+ 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 000000000..20918e55e
--- /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 e8772c7dc..b380c40af 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
@@ -26,12 +26,14 @@
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
+import java.util.function.Consumer;
import java.util.function.UnaryOperator;
import net.kyori.adventure.text.Component;
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 gson component serializer.
@@ -49,7 +51,7 @@ public interface GsonComponentSerializer extends ComponentSerializer {
@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();
+
+ /**
+ * Completes the building process of {@link Builder}.
+ *
+ * @return a {@link Consumer}
+ * @since 4.8.0
+ */
+ @ApiStatus.Internal
+ @NonNull Consumer builder();
+ }
}
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 785d2f0c2..9bc8fb922 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,8 @@
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
+import java.util.Optional;
+import java.util.function.Consumer;
import java.util.function.UnaryOperator;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.text.BlockNBTComponent;
@@ -35,12 +37,27 @@
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 Consumer BUILDER = SERVICE
+ .map(Provider::builder)
+ .orElseGet(() -> builder -> {
+ // NOOP
+ });
+
+ // We cannot store these fields in GsonComponentSerializerImpl directly due to class initialisation issues.
+ static final class Instances {
+ 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;
@@ -103,9 +120,8 @@ final class GsonComponentSerializerImpl implements GsonComponentSerializer {
return this.serializer().toJsonTree(component);
}
- @NonNull
@Override
- public Builder toBuilder() {
+ public @NonNull Builder toBuilder() {
return new BuilderImpl(this);
}
@@ -115,9 +131,11 @@ static final class BuilderImpl implements Builder {
private boolean emitLegacyHover = false;
BuilderImpl() {
+ BUILDER.accept(this); // let service provider touch the builder before anybody else touches it
}
BuilderImpl(final GsonComponentSerializerImpl serializer) {
+ this();
this.downsampleColor = serializer.downsampleColor;
this.emitLegacyHover = serializer.emitLegacyHover;
this.legacyHoverSerializer = serializer.legacyHoverSerializer;
@@ -144,7 +162,7 @@ static final class BuilderImpl implements Builder {
@Override
public @NonNull GsonComponentSerializer build() {
if(this.legacyHoverSerializer == null) {
- return this.downsampleColor ? LEGACY_INSTANCE : INSTANCE;
+ return this.downsampleColor ? Instances.LEGACY_INSTANCE : Instances.INSTANCE;
} else {
return new GsonComponentSerializerImpl(this.downsampleColor, this.legacyHoverSerializer, this.emitLegacyHover);
}
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 6ab602bb2..a9ddddee9 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,6 +23,7 @@
*/
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;
@@ -34,6 +35,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 legacy component serializer.
@@ -55,7 +57,7 @@ public interface LegacyComponentSerializer extends ComponentSerializer {
@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 c3acd8718..76f7ed4ba 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.TextColor;
import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.text.format.TextFormat;
+import net.kyori.adventure.util.Services;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
@@ -95,8 +98,22 @@ 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 Consumer BUILDER = SERVICE
+ .map(Provider::legacy)
+ .orElseGet(() -> builder -> {
+ // NOOP
+ });
+
+ // We cannot store these fields in LegacyComponentSerializerImpl directly due to class initialisation issues.
+ static final class Instances {
+ static final LegacyComponentSerializer SECTION = SERVICE
+ .map(Provider::legacySection)
+ .orElseGet(() -> new LegacyComponentSerializerImpl(SECTION_CHAR, HEX_CHAR, null, false, false, ComponentFlattener.basic()));
+ static final LegacyComponentSerializer AMPERSAND = SERVICE
+ .map(Provider::legacyAmpersand)
+ .orElseGet(() -> new LegacyComponentSerializerImpl(AMPERSAND_CHAR, HEX_CHAR, null, false, false, ComponentFlattener.basic()));
+ }
private final char character;
private final char hexCharacter;
@@ -471,9 +488,11 @@ 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) {
+ this();
this.character = serializer.character;
this.hexCharacter = serializer.hexCharacter;
this.urlReplacementConfig = serializer.urlReplacementConfig;
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 e4e9a652e..d22a5000d 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;
+ @Deprecated 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.createRealSerializerFromLegacyFunctions(keybind, translatable));
}
- PlainComponentSerializer(final @NonNull ComponentFlattener flattener) {
- this.flattener = flattener;
+ @Deprecated
+ 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 +117,27 @@ public PlainComponentSerializer(final @Nullable Function {
-
/**
* Set the component flattener to use.
*
@@ -145,9 +145,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 b86bbf24c..0c7b5bb75 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,57 @@
*/
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 {
+ @Deprecated static final PlainComponentSerializer INSTANCE = new PlainComponentSerializer();
+
+ private PlainComponentSerializerImpl() {
+ }
+
+ @Deprecated
+ static PlainTextComponentSerializer createRealSerializerFromLegacyFunctions(
+ final @Nullable Function keybind,
+ final @Nullable Function translatable
+ ) {
+ // if both legacy functions are null we can simply use the standard plain-text serializer, since there is nothing special we need to do
+ 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();
+ }
+ @Deprecated
static final class BuilderImpl implements PlainComponentSerializer.Builder {
- private ComponentFlattener flattener;
+ private final PlainTextComponentSerializer.Builder builder = PlainTextComponentSerializer.builder();
+ @Deprecated
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;
+ @Deprecated
+ 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 000000000..88c9b20ea
--- /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.Instances.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 000000000..b264f5a8c
--- /dev/null
+++ b/text-serializer-plain/src/main/java/net/kyori/adventure/text/serializer/plain/PlainTextComponentSerializerImpl.java
@@ -0,0 +1,93 @@
+/*
+ * 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 Consumer BUILDER = SERVICE
+ .map(Provider::plainText)
+ .orElseGet(() -> builder -> {
+ // NOOP
+ });
+ final ComponentFlattener flattener;
+
+ // We cannot store these fields in PlainTextComponentSerializerImpl directly due to class initialisation issues.
+ static final class Instances {
+ static final PlainTextComponentSerializer INSTANCE = SERVICE
+ .map(Provider::plainTextSimple)
+ .orElseGet(() -> new PlainTextComponentSerializerImpl(DEFAULT_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();
+ 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 49fb077e6..a17a9d598 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));
}
}