PocketMine-MP 5.15.1 git-be6754494fdbbb9dd57c058ba0e33a4a78c4581f
CompoundTag.php
1<?php
2
3/*
4 *
5 * ____ _ _ __ __ _ __ __ ____
6 * | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7 * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8 * | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9 * |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10 *
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * @author PocketMine Team
17 * @link http://www.pocketmine.net/
18 *
19 *
20*/
21
22declare(strict_types=1);
23
24namespace pocketmine\nbt\tag;
25
32use function count;
33use function func_num_args;
34use function get_class;
35use function is_int;
36use function str_repeat;
37use function strval;
38
42final class CompoundTag extends Tag implements \Countable, \IteratorAggregate{
43 use NoDynamicFieldsTrait;
44
46 private $value = [];
47
48 public function __construct(){
49 self::restrictArgCount(__METHOD__, func_num_args(), 0);
50 }
51
55 public static function create() : self{
56 return new self;
57 }
58
59 public function count() : int{
60 return count($this->value);
61 }
62
66 public function getCount(){
67 return count($this->value);
68 }
69
73 public function getValue(){
74 return $this->value;
75 }
76
77 /*
78 * Here follows many functions of misery for the sake of type safety. We really needs generics in PHP :(
79 */
80
84 public function getTag(string $name) : ?Tag{
85 return $this->value[$name] ?? null;
86 }
87
92 public function getListTag(string $name) : ?ListTag{
93 $tag = $this->getTag($name);
94 if($tag !== null && !($tag instanceof ListTag)){
95 throw new UnexpectedTagTypeException("Expected a tag of type " . ListTag::class . ", got " . get_class($tag));
96 }
97 return $tag;
98 }
99
104 public function getCompoundTag(string $name) : ?CompoundTag{
105 $tag = $this->getTag($name);
106 if($tag !== null && !($tag instanceof CompoundTag)){
107 throw new UnexpectedTagTypeException("Expected a tag of type " . CompoundTag::class . ", got " . get_class($tag));
108 }
109 return $tag;
110 }
111
117 public function setTag(string $name, Tag $tag) : self{
118 $this->value[$name] = $tag;
119 return $this;
120 }
121
126 public function removeTag(string ...$names) : void{
127 foreach($names as $name){
128 unset($this->value[$name]);
129 }
130 }
131
146 private function getTagValue(string $name, string $expectedClass, $default = null){
147 $tag = $this->getTag($name);
148 if($tag instanceof $expectedClass){
149 return $tag->getValue();
150 }
151 if($tag !== null){
152 throw new UnexpectedTagTypeException("Expected a tag of type $expectedClass, got " . get_class($tag));
153 }
154
155 if($default === null){
156 throw new NoSuchTagException("Tag \"$name\" does not exist");
157 }
158
159 return $default;
160 }
161
162 /*
163 * The following methods are wrappers around getTagValue() with type safety.
164 */
165
166 public function getByte(string $name, ?int $default = null) : int{
167 return $this->getTagValue($name, ByteTag::class, $default);
168 }
169
170 public function getShort(string $name, ?int $default = null) : int{
171 return $this->getTagValue($name, ShortTag::class, $default);
172 }
173
174 public function getInt(string $name, ?int $default = null) : int{
175 return $this->getTagValue($name, IntTag::class, $default);
176 }
177
178 public function getLong(string $name, ?int $default = null) : int{
179 return $this->getTagValue($name, LongTag::class, $default);
180 }
181
182 public function getFloat(string $name, ?float $default = null) : float{
183 return $this->getTagValue($name, FloatTag::class, $default);
184 }
185
186 public function getDouble(string $name, ?float $default = null) : float{
187 return $this->getTagValue($name, DoubleTag::class, $default);
188 }
189
190 public function getByteArray(string $name, ?string $default = null) : string{
191 return $this->getTagValue($name, ByteArrayTag::class, $default);
192 }
193
194 public function getString(string $name, ?string $default = null) : string{
195 return $this->getTagValue($name, StringTag::class, $default);
196 }
197
203 public function getIntArray(string $name, ?array $default = null) : array{
204 return $this->getTagValue($name, IntArrayTag::class, $default);
205 }
206
207 /*
208 * The following methods are wrappers around setTag() which create appropriate tag objects on the fly.
209 */
210
214 public function setByte(string $name, int $value) : self{
215 return $this->setTag($name, new ByteTag($value));
216 }
217
221 public function setShort(string $name, int $value) : self{
222 return $this->setTag($name, new ShortTag($value));
223 }
224
228 public function setInt(string $name, int $value) : self{
229 return $this->setTag($name, new IntTag($value));
230 }
231
235 public function setLong(string $name, int $value) : self{
236 return $this->setTag($name, new LongTag($value));
237 }
238
242 public function setFloat(string $name, float $value) : self{
243 return $this->setTag($name, new FloatTag($value));
244 }
245
249 public function setDouble(string $name, float $value) : self{
250 return $this->setTag($name, new DoubleTag($value));
251 }
252
256 public function setByteArray(string $name, string $value) : self{
257 return $this->setTag($name, new ByteArrayTag($value));
258 }
259
263 public function setString(string $name, string $value) : self{
264 return $this->setTag($name, new StringTag($value));
265 }
266
272 public function setIntArray(string $name, array $value) : self{
273 return $this->setTag($name, new IntArrayTag($value));
274 }
275
276 protected function getTypeName() : string{
277 return "Compound";
278 }
279
280 public function getType() : int{
281 return NBT::TAG_Compound;
282 }
283
284 public static function read(NbtStreamReader $reader, ReaderTracker $tracker) : self{
285 $result = new self;
286 $tracker->protectDepth(static function() use($reader, $tracker, $result) : void{
287 for($type = $reader->readByte(); $type !== NBT::TAG_End; $type = $reader->readByte()){
288 $name = $reader->readString();
289 $tag = NBT::createTag($type, $reader, $tracker);
290 if($result->getTag($name) !== null){
291 //this is technically a corruption case, but it's very common on older PM worlds (pretty much every
292 //furnace in PM worlds prior to 2017 is affected), and since we can't extricate this borked data
293 //from the rest in Anvil/McRegion worlds, we can't barf on this - it would result in complete loss
294 //of the chunk.
295 //TODO: add a flag to enable throwing on this (strict mode)
296 continue;
297 }
298 $result->setTag($name, $tag);
299 }
300 });
301 return $result;
302 }
303
304 public function write(NbtStreamWriter $writer) : void{
305 foreach($this->value as $name => $tag){
306 if(is_int($name)){
307 //PHP sucks
308 //we only cast on seeing an int, because forcibly casting other types might conceal bugs.
309 $name = (string) $name;
310 }
311 $writer->writeByte($tag->getType());
312 $writer->writeString($name);
313 $tag->write($writer);
314 }
315 $writer->writeByte(NBT::TAG_End);
316 }
317
318 protected function stringifyValue(int $indentation) : string{
319 $str = "{\n";
320 foreach($this->value as $name => $tag){
321 $str .= str_repeat(" ", $indentation + 1) . "\"$name\" => " . $tag->toString($indentation + 1) . "\n";
322 }
323 return $str . str_repeat(" ", $indentation) . "}";
324 }
325
326 public function __clone(){
327 foreach($this->value as $key => $tag){
328 $this->value[$key] = $tag->safeClone();
329 }
330 }
331
332 protected function makeCopy(){
333 return clone $this;
334 }
335
340 public function getIterator() : \Generator{
341 foreach($this->value as $name => $tag){
342 // PHP arrays are idiotic and cast keys like "1" to int(1)
343 // this also stops us using "yield from". REEEEEEEEEE
344 yield strval($name) => $tag;
345 }
346 }
347
348 public function equals(Tag $that) : bool{
349 if(!($that instanceof $this) or $this->count() !== $that->count()){
350 return false;
351 }
352
353 foreach($this as $k => $v){
354 $other = $that->getTag($k);
355 if($other === null or !$v->equals($other)){
356 return false;
357 }
358 }
359
360 return true;
361 }
362
369 public function merge(CompoundTag $other) : CompoundTag{
370 $new = clone $this;
371
372 foreach($other as $k => $namedTag){
373 $new->setTag($k, clone $namedTag);
374 }
375
376 return $new;
377 }
378}
setString(string $name, string $value)
setByte(string $name, int $value)
setIntArray(string $name, array $value)
setInt(string $name, int $value)
getIntArray(string $name, ?array $default=null)
merge(CompoundTag $other)
setLong(string $name, int $value)
setTag(string $name, Tag $tag)
removeTag(string ... $names)
setDouble(string $name, float $value)
setFloat(string $name, float $value)
setShort(string $name, int $value)
setByteArray(string $name, string $value)