001/* 002 * Copyright 2020 zml 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package ca.stellardrift.confabricate.typeserializers; 017 018import static java.util.Objects.requireNonNull; 019 020import ca.stellardrift.confabricate.mixin.FluidTagsAccessor; 021import com.google.common.collect.ImmutableSet; 022import com.google.errorprone.annotations.concurrent.LazyInit; 023import com.mojang.serialization.Codec; 024import com.mojang.serialization.DynamicOps; 025import io.leangen.geantyref.GenericTypeReflector; 026import io.leangen.geantyref.TypeFactory; 027import io.leangen.geantyref.TypeToken; 028import net.fabricmc.api.EnvType; 029import net.fabricmc.api.Environment; 030import net.minecraft.block.Block; 031import net.minecraft.client.MinecraftClient; 032import net.minecraft.entity.EntityType; 033import net.minecraft.fluid.Fluid; 034import net.minecraft.item.Item; 035import net.minecraft.item.ItemStack; 036import net.minecraft.nbt.CompoundTag; 037import net.minecraft.server.MinecraftServer; 038import net.minecraft.tag.BlockTags; 039import net.minecraft.tag.EntityTypeTags; 040import net.minecraft.tag.ItemTags; 041import net.minecraft.tag.Tag; 042import net.minecraft.tag.TagGroup; 043import net.minecraft.text.Text; 044import net.minecraft.util.Identifier; 045import net.minecraft.util.registry.Registry; 046import net.minecraft.world.dimension.DimensionType; 047import org.apache.logging.log4j.LogManager; 048import org.apache.logging.log4j.Logger; 049import org.checkerframework.checker.nullness.qual.Nullable; 050import org.spongepowered.configurate.ConfigurationNode; 051import org.spongepowered.configurate.extra.dfu.v4.ConfigurateOps; 052import org.spongepowered.configurate.extra.dfu.v4.DfuSerializers; 053import org.spongepowered.configurate.serialize.TypeSerializer; 054import org.spongepowered.configurate.serialize.TypeSerializerCollection; 055 056import java.lang.reflect.Field; 057import java.lang.reflect.Modifier; 058import java.lang.reflect.ParameterizedType; 059import java.lang.reflect.Type; 060import java.util.AbstractMap; 061import java.util.Map; 062import java.util.Set; 063import java.util.function.Supplier; 064 065/** 066 * Access serializers for Minecraft types. 067 * 068 * <p>The {@link #collection()} provides an easily accessible collection of 069 * built-in type serializers, while other factory methods allow creating custom 070 * type serializers that interact with game serialization mechanisms. 071 * 072 * @since 1.2.0 073 */ 074public final class MinecraftSerializers { 075 076 // impl note: initialization order is critical here to ensure we can test 077 // most parts of Confabricate without having to fully initialize Minecraft 078 // and use any of our Mixins 079 080 /** 081 * Registries that should not be added to a serializer collection. 082 */ 083 private static final ImmutableSet<Registry<?>> SPECIAL_REGISTRIES = 084 ImmutableSet.of(Registry.CUSTOM_STAT, // Type of identifier 085 Registry.FLUID, 086 Registry.BLOCK, 087 Registry.ITEM, 088 Registry.ENTITY_TYPE 089 ); 090 091 private static @LazyInit Set<Map.Entry<Type, TypeSerializer<?>>> KNOWN_REGISTRIES; 092 093 private static final TypeToken<Registry<?>> TYPE_REGISTRY_GENERIC = new TypeToken<Registry<?>>() {}; 094 private static final Logger LOGGER = LogManager.getLogger(); 095 096 private static @LazyInit TypeSerializerCollection MINECRAFT_COLLECTION; 097 private static @LazyInit ConfigurateOps DEFAULT_OPS; 098 099 private MinecraftSerializers() {} 100 101 static DynamicOps<ConfigurationNode> opsFor(final ConfigurationNode node) { 102 if (node.options().serializers().equals(MINECRAFT_COLLECTION)) { 103 final @Nullable ConfigurateOps ops = DEFAULT_OPS; 104 if (ops == null) { 105 return DEFAULT_OPS = ConfigurateOps.builder() 106 .factoryFromSerializers(collection()) 107 .readWriteProtection(ConfigurateOps.Protection.NONE) 108 .build(); 109 } 110 return DEFAULT_OPS; 111 } else { 112 return ConfigurateOps.builder() 113 .factoryFromNode(node) 114 .readWriteProtection(ConfigurateOps.Protection.NONE) 115 .build(); 116 } 117 } 118 119 /** 120 * Create a new serializer wrapping the provided {@link Codec}. 121 * 122 * @param codec codec to use for the serialization operation 123 * @param <V> value type 124 * @return a new serializer 125 * @see DfuSerializers#serializer(Codec) for more information 126 * @since 2.0.0 127 */ 128 public static <V> TypeSerializer<V> serializer(final Codec<V> codec) { 129 return DfuSerializers.serializer(codec); 130 } 131 132 /** 133 * Create a new codec that uses the default type serializer collection 134 * to serialize an object of the provided type. 135 * 136 * @param type token representing a value type 137 * @param <S> value type 138 * @return a codec for the type, or null if an appropriate 139 * {@link TypeSerializer} could not be found. 140 * @see DfuSerializers#codec(TypeToken) for more information 141 * @since 2.0.0 142 */ 143 public static <S> @Nullable Codec<S> codec(final TypeToken<S> type) { 144 return codec(type, collection()); 145 } 146 147 /** 148 * Create a new codec based on a Configurate {@link TypeSerializer}. 149 * 150 * @param type type to serialize 151 * @param collection source for values 152 * @param <V> value type 153 * @return a codec, or null if an appropriate {@link TypeSerializer} 154 * could not be found for the TypeToken. 155 * @see DfuSerializers#codec(TypeToken, TypeSerializerCollection) 156 * @since 2.0.0 157 */ 158 public static <V> @Nullable Codec<V> codec(final TypeToken<V> type, final TypeSerializerCollection collection) { 159 return DfuSerializers.codec(type, collection); 160 } 161 162 /** 163 * Create a {@link TypeSerializer} than can interpret values in the 164 * provided registry. 165 * 166 * @param registry the registry 167 * @param <T> the type registered by the registry 168 * @return a serializer for the registry 169 * @since 1.2.0 170 */ 171 public static <T> TypeSerializer<T> forRegistry(final Registry<T> registry) { 172 return new RegistrySerializer<>(registry); 173 } 174 175 /** 176 * The default collection of game serializers. 177 * 178 * <p>While the collection is mutable, modifying it is discouraged in favor 179 * of working with a new child, created with 180 * {@link TypeSerializerCollection#childBuilder()} ()}. Collections of serializers 181 * will become immutable in Configurate 4.0 182 * 183 * @return minecraft serializers 184 * @see #populate(TypeSerializerCollection.Builder) for information about 185 * which serializers this collection will include 186 * @since 1.2.0 187 */ 188 public static TypeSerializerCollection collection() { 189 TypeSerializerCollection collection = MINECRAFT_COLLECTION; 190 if (collection == null) { 191 collection = MINECRAFT_COLLECTION = populate(TypeSerializerCollection.defaults().childBuilder()).build(); 192 } 193 return collection; 194 } 195 196 /** 197 * Check if a collection is our populated collection without attempting to 198 * initialize serializers. 199 * 200 * <p>This helps to integrate with Confabricate in test environments. 201 * 202 * @param collection collection to test 203 * @return if tested collection is the confabricate default collection 204 * @since 1.2.0 205 */ 206 public static boolean isCommonCollection(final TypeSerializerCollection collection) { 207 return requireNonNull(collection, "collection").equals(MINECRAFT_COLLECTION); 208 } 209 210 private static boolean shouldRegister(final Registry<?> registry) { 211 // Don't register root registry -- its key can't be looked up :( 212 return !SPECIAL_REGISTRIES.contains(registry); 213 } 214 215 /** 216 * Register Minecraft {@link TypeSerializer}s with the provided collection. 217 * 218 * <p>This will add serializers for: <ul> 219 * <li>{@link net.minecraft.util.Identifier Identifiers}</li> 220 * <li>{@link net.minecraft.text.Text} (as a string)</li> 221 * <li>Any elements in vanilla {@link Registry registries}</li> 222 * <li>{@link Tag} of a combination of identifiers and 223 * tags for blocks, items, fluids, and entity types</li> 224 * <li>{@link ItemStack}</li> 225 * <li>{@link CompoundTag} instances</li> 226 * </ul> 227 * 228 * @param collection to populate 229 * @return provided collection 230 * @since 1.2.0 231 */ 232 public static TypeSerializerCollection.Builder populate(final TypeSerializerCollection.Builder collection) { 233 collection.registerExact(Identifier.class, IdentifierSerializer.INSTANCE) 234 .register(Text.class, TextSerializer.INSTANCE); 235 236 for (Map.Entry<Type, TypeSerializer<?>> registry : knownRegistries()) { 237 registerRegistry(collection, registry.getKey(), registry.getValue()); 238 } 239 240 collection.register(ItemStack.class, serializer(ItemStack.CODEC)); 241 collection.register(CompoundTag.class, serializer(CompoundTag.CODEC)); 242 243 // All registries here should be in SPECIAL_REGISTRIES 244 populateTaggedRegistry(collection, TypeToken.get(Fluid.class), Registry.FLUID, FluidTagsAccessor.getRequiredTags()::getGroup); 245 populateTaggedRegistry(collection, TypeToken.get(Block.class), Registry.BLOCK, BlockTags::getTagGroup); 246 populateTaggedRegistry(collection, new TypeToken<EntityType<?>>() {}, Registry.ENTITY_TYPE, EntityTypeTags::getTagGroup); 247 populateTaggedRegistry(collection, TypeToken.get(Item.class), Registry.ITEM, ItemTags::getTagGroup); 248 249 return collection; 250 } 251 252 // Type serializers that use the server resource manager 253 private static TypeSerializerCollection.Builder populateServer(final MinecraftServer server, final TypeSerializerCollection.Builder collection) { 254 registerRegistry(collection, DimensionType.class, forRegistry(server.getRegistryManager().getDimensionTypes())); 255 return collection; 256 } 257 258 // Type serializers that source registry + tag information from the client 259 @Environment(EnvType.CLIENT) 260 private static TypeSerializerCollection.Builder populateClient(final MinecraftClient client, final TypeSerializerCollection.Builder collection) { 261 return collection; 262 } 263 264 /** 265 * Lazily initialize our set of vanilla registries. 266 * 267 * <p>This is moderately expensive due to having to reflectively analyze 268 * the fields in the Registry class, so we cache the created serializers 269 * after the first initialization. 270 * 271 * @return collection of built-in registries 272 */ 273 private static Set<Map.Entry<Type, TypeSerializer<?>>> knownRegistries() { 274 Set<Map.Entry<Type, TypeSerializer<?>>> registries = KNOWN_REGISTRIES; 275 if (registries == null) { 276 final ImmutableSet.Builder<Map.Entry<Type, TypeSerializer<?>>> accumulator = ImmutableSet.builder(); 277 for (Field registryField : Registry.class.getFields()) { 278 // only look at public static fields (excludes the ROOT registry) 279 if ((registryField.getModifiers() & (Modifier.STATIC | Modifier.PUBLIC)) != (Modifier.STATIC | Modifier.PUBLIC)) { 280 continue; 281 } 282 283 final Type fieldType = registryField.getGenericType(); 284 if (!GenericTypeReflector.isSuperType(TYPE_REGISTRY_GENERIC.getType(), fieldType)) { // if not a registry (keys) 285 continue; 286 } 287 288 final Type elementType = ((ParameterizedType) fieldType).getActualTypeArguments()[0]; 289 try { 290 final Registry<?> registry = (Registry<?>) registryField.get(null); 291 if (shouldRegister(registry)) { 292 accumulator.add(new AbstractMap.SimpleImmutableEntry<>(elementType, forRegistry(registry))); 293 LOGGER.debug("Created serializer for Minecraft registry {} with element type {}", registry, elementType); 294 } 295 } catch (final IllegalAccessException e) { 296 LOGGER.error("Unable to create serializer for registry of type {} due to access error", elementType, e); 297 } 298 } 299 registries = KNOWN_REGISTRIES = accumulator.build(); 300 } 301 return registries; 302 } 303 304 // Limit scope of warning suppression for reflective registry initialization 305 @SuppressWarnings({"unchecked", "rawtypes"}) 306 private static void registerRegistry(final TypeSerializerCollection.Builder collection, final Type type, final TypeSerializer<?> registry) { 307 collection.registerExact(TypeToken.get(type), (TypeSerializer) registry); 308 } 309 310 /** 311 * Add a serializer for the registry and its {@link Tag}. 312 * 313 * @param collection the collection to serialize 314 * @param token generic element type of the registry 315 * @param registry registry containing values 316 * @param tagRegistry tag container for values in the registry 317 * @param <T> element type 318 */ 319 @SuppressWarnings("unchecked") 320 private static <T> void populateTaggedRegistry(final TypeSerializerCollection.Builder collection, final TypeToken<T> token, 321 final Registry<T> registry, 322 final Supplier<TagGroup<T>> tagRegistry) { 323 final Type tagType = TypeFactory.parameterizedClass(Tag.class, token.getType()); 324 325 collection.registerExact((TypeToken<Tag<T>>) TypeToken.get(tagType), new TagSerializer<>(registry, tagRegistry)); 326 collection.registerExact(token, new RegistrySerializer<>(registry)); 327 } 328 329}