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; 017 018import net.minecraft.nbt.ByteArrayTag; 019import net.minecraft.nbt.ByteTag; 020import net.minecraft.nbt.CompoundTag; 021import net.minecraft.nbt.DoubleTag; 022import net.minecraft.nbt.EndTag; 023import net.minecraft.nbt.FloatTag; 024import net.minecraft.nbt.IntArrayTag; 025import net.minecraft.nbt.IntTag; 026import net.minecraft.nbt.ListTag; 027import net.minecraft.nbt.LongArrayTag; 028import net.minecraft.nbt.LongTag; 029import net.minecraft.nbt.ShortTag; 030import net.minecraft.nbt.StringTag; 031import net.minecraft.nbt.Tag; 032import org.checkerframework.checker.nullness.qual.NonNull; 033import org.spongepowered.configurate.BasicConfigurationNode; 034import org.spongepowered.configurate.ConfigurationNode; 035import org.spongepowered.configurate.ConfigurationNodeFactory; 036import org.spongepowered.configurate.ConfigurationOptions; 037 038import java.io.IOException; 039import java.util.List; 040import java.util.Map; 041import java.util.Set; 042 043/** 044 * A configuration adapter that will convert Minecraft NBT data into a 045 * Configurate {@link ConfigurationNode}. 046 * 047 * @since 1.0.0 048 */ 049public final class NbtNodeAdapter { 050 051 private static final ConfigurationNodeFactory<BasicConfigurationNode> FACTORY = new ConfigurationNodeFactory<>() { 052 053 @Override 054 public BasicConfigurationNode createNode(final ConfigurationOptions options) { 055 return BasicConfigurationNode.root(options 056 .nativeTypes(Set.of(Map.class, List.class, Byte.class, 057 Short.class, Integer.class, Long.class, Float.class, Double.class, 058 long[].class, byte[].class, int[].class, String.class))); 059 } 060 061 }; 062 063 private NbtNodeAdapter() {} 064 065 /** 066 * Given a tag, convert it to a node. 067 * 068 * <p>Depending on the configuration of the provided node, the conversion 069 * may lose some data when roundtripped back. For example, array tags may 070 * be converted to lists if the node provided does not support arrays. 071 * 072 * @param tag the tag to convert 073 * @param node the node to populate 074 * @throws IOException if invalid tags are provided 075 * @since 1.0.0 076 */ 077 public static void tagToNode(final Tag tag, final ConfigurationNode node) throws IOException { 078 if (tag instanceof final CompoundTag compoundTag) { 079 for (final String key : compoundTag.getAllKeys()) { 080 tagToNode(compoundTag.get(key), node.node(key)); 081 } 082 } else if (tag instanceof final ListTag list) { 083 for (final Tag value : list) { 084 tagToNode(value, node.appendListNode()); 085 } 086 } else if (tag instanceof StringTag) { 087 node.raw(tag.getAsString()); 088 } else if (tag instanceof final ByteTag b) { 089 node.raw(b.getAsByte()); 090 } else if (tag instanceof final ShortTag s) { 091 node.raw(s.getAsShort()); 092 } else if (tag instanceof final IntTag i) { 093 node.raw(i.getAsInt()); 094 } else if (tag instanceof final LongTag l) { 095 node.raw(l.getAsLong()); 096 } else if (tag instanceof final FloatTag f) { 097 node.raw(f.getAsFloat()); 098 } else if (tag instanceof final DoubleTag d) { 099 node.raw(d.getAsDouble()); 100 } else if (tag instanceof final ByteArrayTag arr) { 101 if (node.options().acceptsType(byte[].class)) { 102 node.raw(arr.getAsByteArray()); 103 } else { 104 node.raw(null); 105 for (final byte b : arr.getAsByteArray()) { 106 node.appendListNode().raw(b); 107 } 108 } 109 } else if (tag instanceof final IntArrayTag arr) { 110 if (node.options().acceptsType(int[].class)) { 111 node.raw(arr.getAsIntArray()); 112 } else { 113 node.raw(null); 114 for (final int i : arr.getAsIntArray()) { 115 node.appendListNode().raw(i); 116 } 117 } 118 119 } else if (tag instanceof final LongArrayTag arr) { 120 if (node.options().acceptsType(long[].class)) { 121 node.raw(arr.getAsLongArray()); 122 } else { 123 node.raw(null); 124 for (final long l : arr.getAsLongArray()) { 125 node.appendListNode().raw(l); 126 } 127 } 128 } else if (tag instanceof EndTag) { 129 // no-op 130 } else { 131 throw new IOException("Unknown tag type: " + tag.getClass()); 132 } 133 } 134 135 /** 136 * Convert a node to tag. Because NBT is strongly typed and does not permit 137 * lists with mixed types, some configuration nodes will not be convertible 138 * to Tags. 139 * 140 * @param node the configuration node 141 * @return the converted tag object 142 * @throws IOException if an IO error occurs while converting the tag 143 * @since 1.0.0 144 */ 145 public static Tag nodeToTag(final ConfigurationNode node) throws IOException { 146 if (node.isMap()) { 147 final CompoundTag tag = new CompoundTag(); 148 for (final Map.Entry<Object, ? extends ConfigurationNode> ent : node.childrenMap().entrySet()) { 149 tag.put(ent.getKey().toString(), nodeToTag(ent.getValue())); 150 } 151 return tag; 152 } else if (node.isList()) { 153 final ListTag list = new ListTag(); 154 for (final ConfigurationNode child : node.childrenList()) { 155 list.add(nodeToTag(child)); 156 } 157 return list; 158 } else { 159 final Object obj = node.raw(); 160 if (obj instanceof final byte[] arr) { 161 return new ByteArrayTag(arr); 162 } else if (obj instanceof final int[] arr) { 163 return new IntArrayTag(arr); 164 } else if (obj instanceof final long[] arr) { 165 return new LongArrayTag(arr); 166 } else if (obj instanceof final Byte b) { 167 return ByteTag.valueOf(b); 168 } else if (obj instanceof final Short s) { 169 return ShortTag.valueOf(s); 170 } else if (obj instanceof final Integer i) { 171 return IntTag.valueOf(i); 172 } else if (obj instanceof final Long l) { 173 return LongTag.valueOf(l); 174 } else if (obj instanceof final Float f) { 175 return FloatTag.valueOf(f); 176 } else if (obj instanceof final Double d) { 177 return DoubleTag.valueOf(d); 178 } else if (obj instanceof final String s) { 179 return StringTag.valueOf(s); 180 } else { 181 throw new IOException("Unsupported object type " + (obj == null ? null : obj.getClass())); 182 } 183 } 184 } 185 186 /** 187 * Create an empty node with options appropriate for handling NBT data. 188 * 189 * @return the new node 190 * @since 1.0.0 191 */ 192 public static ConfigurationNode createEmptyNode() { 193 return FACTORY.createNode(Confabricate.confabricateOptions()); 194 } 195 196 /** 197 * Create an empty node with options appropriate for handling NBT data. 198 * 199 * @param options options to work with 200 * @return the new node 201 * @since 1.0.0 202 */ 203 public static ConfigurationNode createEmptyNode(final @NonNull ConfigurationOptions options) { 204 return FACTORY.createNode(options); 205 } 206 207 /** 208 * Get a factory for nodes prepared to handle NBT data. 209 * 210 * @return the factory 211 * @since 3.0.0 212 */ 213 public static ConfigurationNodeFactory<BasicConfigurationNode> nodeFactory() { 214 return FACTORY; 215 } 216 217}