Skip to content

Commit

Permalink
feat(text-serializer-gson): Use feature flags to control serializatio…
Browse files Browse the repository at this point in the history
…n style
  • Loading branch information
zml2008 committed Dec 7, 2023
1 parent d211fc3 commit 5d161b1
Show file tree
Hide file tree
Showing 7 changed files with 236 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
import net.kyori.adventure.text.StorageNBTComponent;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.TranslatableComponent;
import net.kyori.adventure.util.flag.FeatureFlagSet;
import org.jetbrains.annotations.Nullable;

import static net.kyori.adventure.text.serializer.json.JSONComponentConstants.EXTRA;
Expand All @@ -74,13 +75,15 @@
final class ComponentSerializerImpl extends TypeAdapter<Component> {
static final Type COMPONENT_LIST_TYPE = new TypeToken<List<Component>>() {}.getType();

static TypeAdapter<Component> create(final Gson gson) {
return new ComponentSerializerImpl(gson).nullSafe();
static TypeAdapter<Component> create(final FeatureFlagSet flags, final Gson gson) {
return new ComponentSerializerImpl(flags.value(GsonFlags.EMIT_COMPACT_TEXT_COMPONENT), gson).nullSafe();
}

private final boolean emitCompactTextComponent;
private final Gson gson;

private ComponentSerializerImpl(final Gson gson) {
private ComponentSerializerImpl(final boolean emitCompactTextComponent, final Gson gson) {
this.emitCompactTextComponent = emitCompactTextComponent;
this.gson = gson;
}

Expand Down Expand Up @@ -230,6 +233,16 @@ private static <C extends NBTComponent<C, B>, B extends NBTComponentBuilder<C, B

@Override
public void write(final JsonWriter out, final Component value) throws IOException {
if (
value instanceof TextComponent
&& value.children().isEmpty()
&& !value.hasStyling()
&& this.emitCompactTextComponent
) {
out.value(((TextComponent) value).content());
return;
}

out.beginObject();

if (value.hasStyling()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import net.kyori.adventure.text.serializer.json.JSONComponentSerializer;
import net.kyori.adventure.util.Buildable;
import net.kyori.adventure.util.PlatformAPI;
import net.kyori.adventure.util.flag.FeatureFlagSet;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
Expand Down Expand Up @@ -122,14 +123,38 @@ static Builder builder() {
* @since 4.0.0
*/
interface Builder extends AbstractBuilder<GsonComponentSerializer>, Buildable.Builder<GsonComponentSerializer>, JSONComponentSerializer.Builder {
/**
* Set the feature flag set to apply on this serializer.
*
* <p>This controls how the serializer emits and interprets components.</p>
*
* @param flags the flag set to use
* @return this builder
* @see GsonFlags
* @since 4.15.0
*/
@NotNull Builder featureFlags(final @NotNull FeatureFlagSet flags);

/**
* Edit the active set of feature flags.
*
* @param flagEditor the consumer operating on the existing flag set
* @return this builder
* @see GsonFlags
* @since 4.15.0
*/
@NotNull Builder editFlags(final @NotNull Consumer<FeatureFlagSet.Builder> flagEditor);

/**
* Sets that the serializer should downsample hex colors to named colors.
*
* @return this builder
* @since 4.0.0
*/
@Override
@NotNull Builder downsampleColors();
default @NotNull Builder downsampleColors() {
return this.editFlags(flags -> flags.value(GsonFlags.EMIT_RGB, false));
}

/**
* Sets a serializer that will be used to interpret legacy hover event {@code value} payloads.
Expand All @@ -155,7 +180,9 @@ interface Builder extends AbstractBuilder<GsonComponentSerializer>, Buildable.Bu
* @since 4.0.0
*/
@Override
@NotNull Builder emitLegacyHoverEvent();
default @NotNull Builder emitLegacyHoverEvent() {
return this.editFlags(b -> b.value(GsonFlags.EMIT_LEGACY_HOVER_EVENT, true));
}

/**
* Builds the serializer.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,12 @@
import java.util.function.UnaryOperator;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.util.Services;
import net.kyori.adventure.util.flag.FeatureFlagSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import static java.util.Objects.requireNonNull;

final class GsonComponentSerializerImpl implements GsonComponentSerializer {
private static final Optional<Provider> SERVICE = Services.service(Provider.class);
static final Consumer<Builder> BUILDER = SERVICE
Expand All @@ -46,24 +49,22 @@ final class GsonComponentSerializerImpl implements GsonComponentSerializer {
static final class Instances {
static final GsonComponentSerializer INSTANCE = SERVICE
.map(Provider::gson)
.orElseGet(() -> new GsonComponentSerializerImpl(false, null, false));
.orElseGet(() -> new GsonComponentSerializerImpl(GsonFlags.byProtocolVersion(), null));
static final GsonComponentSerializer LEGACY_INSTANCE = SERVICE
.map(Provider::gsonLegacy)
.orElseGet(() -> new GsonComponentSerializerImpl(true, null, true));
.orElseGet(() -> new GsonComponentSerializerImpl(GsonFlags.byProtocolVersion().at(712 /* just before 1.16 */), null));
}

private final Gson serializer;
private final UnaryOperator<GsonBuilder> populator;
private final boolean downsampleColor;
private final net.kyori.adventure.text.serializer.json.@Nullable LegacyHoverEventSerializer legacyHoverSerializer;
private final boolean emitLegacyHover;
private final FeatureFlagSet flags;

GsonComponentSerializerImpl(final boolean downsampleColor, final net.kyori.adventure.text.serializer.json.@Nullable LegacyHoverEventSerializer legacyHoverSerializer, final boolean emitLegacyHover) {
this.downsampleColor = downsampleColor;
GsonComponentSerializerImpl(final FeatureFlagSet flags, final net.kyori.adventure.text.serializer.json.@Nullable LegacyHoverEventSerializer legacyHoverSerializer) {
this.flags = flags;
this.legacyHoverSerializer = legacyHoverSerializer;
this.emitLegacyHover = emitLegacyHover;
this.populator = builder -> {
builder.registerTypeAdapterFactory(new SerializerFactory(downsampleColor, legacyHoverSerializer, emitLegacyHover));
builder.registerTypeAdapterFactory(new SerializerFactory(flags, legacyHoverSerializer));
return builder;
};
this.serializer = this.populator.apply(
Expand Down Expand Up @@ -120,46 +121,43 @@ static final class Instances {
}

static final class BuilderImpl implements Builder {
private boolean downsampleColor = false;
private FeatureFlagSet flags = GsonFlags.byProtocolVersion(); // latest
private net.kyori.adventure.text.serializer.json.@Nullable LegacyHoverEventSerializer legacyHoverSerializer;
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.flags = serializer.flags;
this.legacyHoverSerializer = serializer.legacyHoverSerializer;
}

@Override
public @NotNull Builder downsampleColors() {
this.downsampleColor = true;
public @NotNull Builder featureFlags(final @NotNull FeatureFlagSet flags) {
this.flags = requireNonNull(flags, "flags");
return this;
}

@Override
public @NotNull Builder legacyHoverEventSerializer(final net.kyori.adventure.text.serializer.json.@Nullable LegacyHoverEventSerializer serializer) {
this.legacyHoverSerializer = serializer;
public @NotNull Builder editFlags(final @NotNull Consumer<FeatureFlagSet.Builder> flagEditor) {
final FeatureFlagSet.Builder builder = FeatureFlagSet.builder()
.values(this.flags);
requireNonNull(flagEditor, "flagEditor").accept(builder);
this.flags = builder.build();
return this;
}

@Override
public @NotNull Builder emitLegacyHoverEvent() {
this.emitLegacyHover = true;
public @NotNull Builder legacyHoverEventSerializer(final net.kyori.adventure.text.serializer.json.@Nullable LegacyHoverEventSerializer serializer) {
this.legacyHoverSerializer = serializer;
return this;
}

@Override
public @NotNull GsonComponentSerializer build() {
if (this.legacyHoverSerializer == null) {
return this.downsampleColor ? Instances.LEGACY_INSTANCE : Instances.INSTANCE;
} else {
return new GsonComponentSerializerImpl(this.downsampleColor, this.legacyHoverSerializer, this.emitLegacyHover);
}
return new GsonComponentSerializerImpl(this.flags, this.legacyHoverSerializer);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
* This file is part of adventure, licensed under the MIT License.
*
* Copyright (c) 2017-2023 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.gson;

import net.kyori.adventure.key.Key;
import net.kyori.adventure.util.flag.FeatureFlag;
import net.kyori.adventure.util.flag.FeatureFlagSet;
import org.jetbrains.annotations.NotNull;

/**
* Feature flags that apply to the Gson serializer.
*
* @since 4.15.0
*/
public final class GsonFlags {
private GsonFlags() {
}

/**
* Whether to emit RGB text.
*
* <p>If this attribute is disabled, colors in styles will be downsampled to the classic 16 colors.</p>
*
* @since 4.15.0
* @sinceMinecraft 1.16
*/
public static final FeatureFlag<Boolean> EMIT_RGB = FeatureFlag.booleanFlag(key("emit/rgb"), true);
/**
* Whether to emit the legacy hover event style used before Minecraft 1.16.
*
* @since 4.15.0
*/
public static final FeatureFlag<Boolean> EMIT_LEGACY_HOVER_EVENT = FeatureFlag.booleanFlag(key("emit/legacy_hover"), false);
/**
* Whether to emit the hover event contents as updated in 1.16.
*
* @since 4.15.0
* @sinceMinecraft 1.16
*/
public static final FeatureFlag<Boolean> EMIT_MODERN_HOVER_EVENT = FeatureFlag.booleanFlag(key("emit/modern_hover"), true);

/**
* Whether to emit text components with no style and no children as plain text.
*
* @since 4.15.0
* @sinceMinecraft 1.20.3
*/
public static final FeatureFlag<Boolean> EMIT_COMPACT_TEXT_COMPONENT = FeatureFlag.booleanFlag(key("emit/compact_text_component"), true);

/**
* Whether to emit the hover event show entity action's entity UUID as an int array,
* as understood by 1.20.3+, or as a string as understood by previous versions.
*/
public static final FeatureFlag<Boolean> EMIT_HOVER_SHOW_ENTITY_ID_AS_INT_ARRAY = FeatureFlag.booleanFlag(key("emit/hover_show_entity_id_as_int_array"), true);

/**
* Versioned by protocol version.
*/
private static final FeatureFlagSet.Versioned BY_PROTOCOL_VERSION = FeatureFlagSet.versionedBuilder()
.version(
0 /* initial */,
b -> b.value(EMIT_LEGACY_HOVER_EVENT, true)
.value(EMIT_RGB, false)
.value(EMIT_MODERN_HOVER_EVENT, false)
.value(EMIT_HOVER_SHOW_ENTITY_ID_AS_INT_ARRAY, false)
)
.version(
713 /* 20w17a, for 1.16 */,
b -> b.value(EMIT_LEGACY_HOVER_EVENT, false)
.value(EMIT_RGB, true)
.value(EMIT_MODERN_HOVER_EVENT, true)
)
.version(
765 /* 1.20.3 */,
b -> b.value(EMIT_COMPACT_TEXT_COMPONENT, true)
.value(EMIT_HOVER_SHOW_ENTITY_ID_AS_INT_ARRAY, true)
)
.build();

/**
* The combination of flags that can be understood by modern clients, as well as as far back as possible.
*
* <p>This may provide a less efficient representation of components</p>
*/
private static final FeatureFlagSet MOST_COMPATIBLE = FeatureFlagSet.builder()
.value(EMIT_LEGACY_HOVER_EVENT, true)
.value(EMIT_HOVER_SHOW_ENTITY_ID_AS_INT_ARRAY, false)
.value(EMIT_COMPACT_TEXT_COMPONENT, false)
.build();

@SuppressWarnings("PatternValidation")
private static Key key(final String value) {
return Key.key("adventure", "gson/" + value);
}

/**
* Get Gson flags delineated by game protocol version.
*
* @return the versioned flag set
* @since 4.15.0
*/
public static FeatureFlagSet.@NotNull Versioned byProtocolVersion() {
return BY_PROTOCOL_VERSION;
}

/**
* The combination of flags that can be understood by modern clients, as well as as far back as possible.
*
* <p>This may provide a less efficient representation of components</p>
*
* @return the most widely compatible feature flag set
* @since 4.15.0
*/
public static @NotNull FeatureFlagSet compatibility() {
return MOST_COMPATIBLE;
}
}
Loading

0 comments on commit 5d161b1

Please sign in to comment.