001/*
002 * Trident - A Multithreaded Server Alternative
003 * Copyright 2014 The TridentSDK Team
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *    http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package net.tridentsdk.meta.nbt;
019
020import com.google.common.base.Charsets;
021import net.tridentsdk.util.TridentLogger;
022
023import java.io.DataInput;
024import java.io.IOException;
025
026/**
027 * @author The TridentSDK Team
028 * @since 0.3-alpha-DP
029 */
030public class NBTDecoder {
031    final DataInput input;
032
033    public NBTDecoder(DataInput input) {
034        this.input = input;
035    }
036
037    public CompoundTag decode() throws NBTException {
038        try {
039            return this.decode(this.input.readByte());
040        } catch (IOException e) {
041            TridentLogger.get().error(new NBTException("IO Error decoding the NBT Data", e));
042            return null;
043        }
044    }
045
046    public CompoundTag decode(byte b) throws NBTException {
047        TagType initType = TagType.fromId(b);
048
049        //NBT source must start with a compound tag or is invalid
050        if (initType != TagType.COMPOUND) {
051            TridentLogger.get().error(new NBTException("NBT Data must start with a Compound Tag."));
052        }
053
054        //Create the resulting CompoundTag to return
055        //Uses recursion to recursively walk through the tag tree
056        try {
057            return this.resolveCompoundTag(this.readString());
058        } catch (IOException e) {
059            TridentLogger.get().error(new NBTException("IO Error decoding the NBT Data", e));
060            return null;
061        }
062    }
063
064    private CompoundTag resolveCompoundTag(String name) throws IOException {
065        CompoundTag compound = new CompoundTag(name);
066        TagType innerType;
067
068        while ((innerType = TagType.fromId(this.input.readByte())) != TagType.END) {
069            compound.addTag(this.resolveTag(innerType, true));
070        }
071
072        return compound;
073    }
074
075    private ListTag resolveListTag(String name) throws IOException {
076        TagType listType = TagType.fromId(this.input.readByte());
077        ListTag list = new ListTag(name, listType);
078        int length = this.input.readInt();
079
080        for (int i = 0; i < length; i++) {
081            list.addTag(this.resolveTag(listType, false));
082        }
083
084        return list;
085    }
086
087    private NBTTag resolveTag(TagType type, boolean withName) throws IOException {
088        //Reads name if required
089        String name = null;
090        if (withName) {
091            name = this.readString();
092        }
093
094        NBTTag result;
095        switch (type) {
096            case BYTE:
097                result = new ByteTag(name);
098                result.asType(ByteTag.class).setValue(this.input.readByte());
099                break;
100
101            case SHORT:
102                result = new ShortTag(name);
103                result.asType(ShortTag.class).setValue(this.input.readShort());
104                break;
105
106            case INT:
107                result = new IntTag(name);
108                result.asType(IntTag.class).setValue(this.input.readInt());
109                break;
110
111            case LONG:
112                result = new LongTag(name);
113                result.asType(LongTag.class).setValue(this.input.readLong());
114                break;
115
116            case FLOAT:
117                result = new FloatTag(name);
118                result.asType(FloatTag.class).setValue(this.input.readFloat());
119                break;
120
121            case DOUBLE:
122                result = new DoubleTag(name);
123                result.asType(DoubleTag.class).setValue(this.input.readDouble());
124                break;
125
126            case BYTE_ARRAY:
127                result = new ByteArrayTag(name);
128                int balength = this.input.readInt();
129                byte[] babytes = new byte[balength];
130
131                this.input.readFully(babytes);
132                result.asType(ByteArrayTag.class).setValue(babytes);
133
134                break;
135
136            case STRING:
137                result = new StringTag(name);
138                result.asType(StringTag.class).setValue(this.readString());
139
140                break;
141
142            case LIST:
143                result = this.resolveListTag(name);
144                break;
145
146            case COMPOUND:
147                result = this.resolveCompoundTag(name);
148                break;
149
150            case INT_ARRAY:
151                result = new IntArrayTag(name);
152                int ialength = this.input.readInt();
153                int[] array = new int[ialength];
154
155                for (int i = 0; i < array.length; i++) {
156                    array[i] = this.input.readInt();
157                }
158
159                result.asType(IntArrayTag.class).setValue(array);
160                break;
161
162            default:
163                result = new NullTag(name);
164                break;
165        }
166
167        return result;
168    }
169
170    private String readString() throws IOException {
171        short length = this.input.readShort();
172        byte[] bytes = new byte[(int) length];
173
174        this.input.readFully(bytes);
175
176        return new String(bytes, Charsets.UTF_8);
177    }
178}