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