PocketMine-MP 5.27.1 git-9af3cde03fabbe4129c79e46dc87ffa0fff446e6
Loading...
Searching...
No Matches
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
33use function count;
34use function func_num_args;
35use function get_class;
36use function is_int;
37use function sprintf;
38use function str_repeat;
39use function strlen;
40use function strval;
41
45final class CompoundTag extends Tag implements \Countable, \IteratorAggregate{
46 use NoDynamicFieldsTrait;
47
49 private $value = [];
50
51 public function __construct(){
52 self::restrictArgCount(__METHOD__, func_num_args(), 0);
53 }
54
58 public static function create() : self{
59 return new self;
60 }
61
62 public function count() : int{
63 return count($this->value);
64 }
65
69 public function getCount(){
70 return count($this->value);
71 }
72
76 public function getValue(){
77 return $this->value;
78 }
79
80 /*
81 * Here follows many functions of misery for the sake of type safety. We really needs generics in PHP :(
82 */
83
87 public function getTag(string $name) : ?Tag{
88 return $this->value[$name] ?? null;
89 }
90
95 public function getListTag(string $name) : ?ListTag{
96 $tag = $this->getTag($name);
97 if($tag !== null && !($tag instanceof ListTag)){
98 throw new UnexpectedTagTypeException("Expected a tag of type " . ListTag::class . ", got " . get_class($tag));
99 }
100 return $tag;
101 }
102
107 public function getCompoundTag(string $name) : ?CompoundTag{
108 $tag = $this->getTag($name);
109 if($tag !== null && !($tag instanceof CompoundTag)){
110 throw new UnexpectedTagTypeException("Expected a tag of type " . CompoundTag::class . ", got " . get_class($tag));
111 }
112 return $tag;
113 }
114
120 public function setTag(string $name, Tag $tag) : self{
121 if(strlen($name) > Limits::INT16_MAX){
122 throw new \InvalidArgumentException(sprintf("Tag name must be at most %d bytes, but got %d bytes", Limits::INT16_MAX, strlen($name)));
123 }
124 $this->value[$name] = $tag;
125 return $this;
126 }
127
132 public function removeTag(string ...$names) : void{
133 foreach($names as $name){
134 unset($this->value[$name]);
135 }
136 }
137
152 private function getTagValue(string $name, string $expectedClass, $default = null){
153 $tag = $this->getTag($name);
154 if($tag instanceof $expectedClass){
155 return $tag->getValue();
156 }
157 if($tag !== null){
158 throw new UnexpectedTagTypeException("Expected a tag of type $expectedClass, got " . get_class($tag));
159 }
160
161 if($default === null){
162 throw new NoSuchTagException("Tag \"$name\" does not exist");
163 }
164
165 return $default;
166 }
167
168 /*
169 * The following methods are wrappers around getTagValue() with type safety.
170 */
171
172 public function getByte(string $name, ?int $default = null) : int{
173 return $this->getTagValue($name, ByteTag::class, $default);
174 }
175
176 public function getShort(string $name, ?int $default = null) : int{
177 return $this->getTagValue($name, ShortTag::class, $default);
178 }
179
180 public function getInt(string $name, ?int $default = null) : int{
181 return $this->getTagValue($name, IntTag::class, $default);
182 }
183
184 public function getLong(string $name, ?int $default = null) : int{
185 return $this->getTagValue($name, LongTag::class, $default);
186 }
187
188 public function getFloat(string $name, ?float $default = null) : float{
189 return $this->getTagValue($name, FloatTag::class, $default);
190 }
191
192 public function getDouble(string $name, ?float $default = null) : float{
193 return $this->getTagValue($name, DoubleTag::class, $default);
194 }
195
196 public function getByteArray(string $name, ?string $default = null) : string{
197 return $this->getTagValue($name, ByteArrayTag::class, $default);
198 }
199
200 public function getString(string $name, ?string $default = null) : string{
201 return $this->getTagValue($name, StringTag::class, $default);
202 }
203
209 public function getIntArray(string $name, ?array $default = null) : array{
210 return $this->getTagValue($name, IntArrayTag::class, $default);
211 }
212
213 /*
214 * The following methods are wrappers around setTag() which create appropriate tag objects on the fly.
215 */
216
220 public function setByte(string $name, int $value) : self{
221 return $this->setTag($name, new ByteTag($value));
222 }
223
227 public function setShort(string $name, int $value) : self{
228 return $this->setTag($name, new ShortTag($value));
229 }
230
234 public function setInt(string $name, int $value) : self{
235 return $this->setTag($name, new IntTag($value));
236 }
237
241 public function setLong(string $name, int $value) : self{
242 return $this->setTag($name, new LongTag($value));
243 }
244
248 public function setFloat(string $name, float $value) : self{
249 return $this->setTag($name, new FloatTag($value));
250 }
251
255 public function setDouble(string $name, float $value) : self{
256 return $this->setTag($name, new DoubleTag($value));
257 }
258
262 public function setByteArray(string $name, string $value) : self{
263 return $this->setTag($name, new ByteArrayTag($value));
264 }
265
269 public function setString(string $name, string $value) : self{
270 return $this->setTag($name, new StringTag($value));
271 }
272
279 public function setIntArray(string $name, array $value) : self{
280 return $this->setTag($name, new IntArrayTag($value));
281 }
282
283 protected function getTypeName() : string{
284 return "Compound";
285 }
286
287 public function getType() : int{
288 return NBT::TAG_Compound;
289 }
290
291 public static function read(NbtStreamReader $reader, ReaderTracker $tracker) : self{
292 $result = new self;
293 $tracker->protectDepth(static function() use($reader, $tracker, $result) : void{
294 for($type = $reader->readByte(); $type !== NBT::TAG_End; $type = $reader->readByte()){
295 $name = $reader->readString();
296 $tag = NBT::createTag($type, $reader, $tracker);
297 if($result->getTag($name) !== null){
298 //this is technically a corruption case, but it's very common on older PM worlds (pretty much every
299 //furnace in PM worlds prior to 2017 is affected), and since we can't extricate this borked data
300 //from the rest in Anvil/McRegion worlds, we can't barf on this - it would result in complete loss
301 //of the chunk.
302 //TODO: add a flag to enable throwing on this (strict mode)
303 continue;
304 }
305 $result->setTag($name, $tag);
306 }
307 });
308 return $result;
309 }
310
311 public function write(NbtStreamWriter $writer) : void{
312 foreach($this->value as $name => $tag){
313 if(is_int($name)){
314 //PHP sucks
315 //we only cast on seeing an int, because forcibly casting other types might conceal bugs.
316 $name = (string) $name;
317 }
318 $writer->writeByte($tag->getType());
319 $writer->writeString($name);
320 $tag->write($writer);
321 }
322 $writer->writeByte(NBT::TAG_End);
323 }
324
325 protected function stringifyValue(int $indentation) : string{
326 $str = "{\n";
327 foreach($this->value as $name => $tag){
328 $str .= str_repeat(" ", $indentation + 1) . "\"$name\" => " . $tag->toString($indentation + 1) . "\n";
329 }
330 return $str . str_repeat(" ", $indentation) . "}";
331 }
332
333 public function __clone(){
334 foreach($this->value as $key => $tag){
335 $this->value[$key] = $tag->safeClone();
336 }
337 }
338
339 protected function makeCopy(){
340 return clone $this;
341 }
342
347 public function getIterator() : \Generator{
348 foreach($this->value as $name => $tag){
349 // PHP arrays are idiotic and cast keys like "1" to int(1)
350 // this also stops us using "yield from". REEEEEEEEEE
351 yield strval($name) => $tag;
352 }
353 }
354
355 public function equals(Tag $that) : bool{
356 if(!($that instanceof $this) or count($this->value) !== count($that->value)){
357 return false;
358 }
359
360 foreach($this->value as $k => $v){
361 $other = $that->value[$k] ?? null;
362 if($other === null or !$v->equals($other)){
363 return false;
364 }
365 }
366
367 return true;
368 }
369
376 public function merge(CompoundTag $other) : CompoundTag{
377 $new = clone $this;
378
379 foreach($other as $k => $namedTag){
380 $new->setTag($k, clone $namedTag);
381 }
382
383 return $new;
384 }
385}
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)
setLong(string $name, int $value)
setTag(string $name, Tag $tag)
setDouble(string $name, float $value)
setFloat(string $name, float $value)
setShort(string $name, int $value)
setByteArray(string $name, string $value)