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