63 $lines[] =
"Renames:";
64 foreach($schema->renamedIds as $rename){
65 $lines[] =
"- $rename";
67 $lines[] =
"Added properties:";
68 foreach(Utils::stringifyKeys($schema->addedProperties) as $blockName => $tags){
69 foreach(Utils::stringifyKeys($tags) as $k => $v){
70 $lines[] =
"- $blockName has $k added: $v";
74 $lines[] =
"Removed properties:";
75 foreach(Utils::stringifyKeys($schema->removedProperties) as $blockName => $tagNames){
76 foreach($tagNames as $tagName){
77 $lines[] =
"- $blockName has $tagName removed";
80 $lines[] =
"Renamed properties:";
81 foreach(Utils::stringifyKeys($schema->renamedProperties) as $blockName => $tagNames){
82 foreach(Utils::stringifyKeys($tagNames) as $oldTagName => $newTagName){
83 $lines[] =
"- $blockName has $oldTagName renamed to $newTagName";
86 $lines[] =
"Remapped property values:";
87 foreach(Utils::stringifyKeys($schema->remappedPropertyValues) as $blockName => $remaps){
88 foreach(Utils::stringifyKeys($remaps) as $tagName => $oldNewList){
89 foreach($oldNewList as $oldNew){
90 $lines[] =
"- $blockName has $tagName value changed from $oldNew->old to $oldNew->new";
94 return implode(
"\n", $lines);
99 if($tag instanceof
IntTag){
103 }elseif($tag instanceof
ByteTag){
106 throw new \UnexpectedValueException(
"Unexpected value type " . get_debug_type($tag));
114 isset($model->byte) && !isset($model->int) && !isset($model->string) =>
new ByteTag($model->byte),
115 !isset($model->byte) && isset($model->int) && !isset($model->string) =>
new IntTag($model->int),
116 !isset($model->byte) && !isset($model->int) && isset($model->string) =>
new StringTag($model->string),
117 default =>
throw new \UnexpectedValueException(
"Malformed JSON model tag, expected exactly one of 'byte', 'int' or 'string' properties")
123 $model->maxVersionMajor,
124 $model->maxVersionMinor,
125 $model->maxVersionPatch,
126 $model->maxVersionRevision,
129 $result->renamedIds = $model->renamedIds ?? [];
130 $result->renamedProperties = $model->renamedProperties ?? [];
131 $result->removedProperties = $model->removedProperties ?? [];
133 foreach(Utils::stringifyKeys($model->addedProperties ?? []) as $blockName => $properties){
134 foreach(Utils::stringifyKeys($properties) as $propertyName => $propertyValue){
135 $result->addedProperties[$blockName][$propertyName] = self::jsonModelToTag($propertyValue);
139 $convertedRemappedValuesIndex = [];
140 foreach(Utils::stringifyKeys($model->remappedPropertyValuesIndex ?? []) as $mappingKey => $mappingValues){
141 foreach($mappingValues as $k => $oldNew){
143 self::jsonModelToTag($oldNew->old),
144 self::jsonModelToTag($oldNew->new)
149 foreach(Utils::stringifyKeys($model->remappedPropertyValues ?? []) as $blockName => $properties){
150 foreach(Utils::stringifyKeys($properties) as $property => $mappedValuesKey){
151 if(!isset($convertedRemappedValuesIndex[$mappedValuesKey])){
152 throw new \UnexpectedValueException(
"Missing key from schema values index $mappedValuesKey");
154 $result->remappedPropertyValues[$blockName][$property] = $convertedRemappedValuesIndex[$mappedValuesKey];
158 foreach(Utils::stringifyKeys($model->flattenedProperties ?? []) as $blockName => $flattenRule){
159 $result->flattenedProperties[$blockName] = self::jsonModelToFlattenRule($flattenRule);
162 foreach(Utils::stringifyKeys($model->remappedStates ?? []) as $oldBlockName => $remaps){
163 foreach($remaps as $remap){
164 if(isset($remap->newName)){
165 $remapName = $remap->newName;
166 }elseif(isset($remap->newFlattenedName)){
167 $flattenRule = $remap->newFlattenedName;
168 $remapName = self::jsonModelToFlattenRule($flattenRule);
170 throw new \UnexpectedValueException(
"Expected exactly one of 'newName' or 'newFlattenedName' properties to be set");
177 $remap->copiedState ?? []
186 if(count($schema->remappedPropertyValues) === 0){
192 $orderedRemappedValues = $schema->remappedPropertyValues;
193 ksort($orderedRemappedValues);
194 foreach(Utils::stringifyKeys($orderedRemappedValues) as $blockName => $remaps){
196 foreach(Utils::stringifyKeys($remaps) as $propertyName => $remappedValues){
197 $remappedValuesMap = [];
198 foreach($remappedValues as $oldNew){
199 $remappedValuesMap[$oldNew->old->toString()] = $oldNew;
201 ksort($remappedValuesMap);
203 if(isset($dedupTableMap[$propertyName])){
204 foreach($dedupTableMap[$propertyName] as $k => $dedupValuesMap){
205 if(count($remappedValuesMap) !== count($dedupValuesMap)){
209 foreach(Utils::stringifyKeys($remappedValuesMap) as $oldHash => $remappedOldNew){
211 !isset($dedupValuesMap[$oldHash]) ||
212 !$remappedOldNew->old->equals($dedupValuesMap[$oldHash]->old) ||
213 !$remappedOldNew->new->equals($dedupValuesMap[$oldHash]->new)
220 $dedupMapping[$blockName][$propertyName] = $k;
226 $dedupTableMap[$propertyName][] = $remappedValuesMap;
227 $dedupMapping[$blockName][$propertyName] = array_key_last($dedupTableMap[$propertyName]);
232 foreach(Utils::stringifyKeys($dedupTableMap) as $propertyName => $mappingSet){
233 foreach($mappingSet as $setId => $valuePairs){
234 $newDedupName = $propertyName .
"_" . str_pad(strval($setId), 2,
"0", STR_PAD_LEFT);
235 foreach($valuePairs as $pair){
237 BlockStateUpgradeSchemaUtils::tagToJsonModel($pair->old),
238 BlockStateUpgradeSchemaUtils::tagToJsonModel($pair->new),
243 $modelDedupMapping = [];
244 foreach(Utils::stringifyKeys($dedupMapping) as $blockName => $properties){
245 foreach(Utils::stringifyKeys($properties) as $propertyName => $dedupTableIndex){
246 $modelDedupMapping[$blockName][$propertyName] = $propertyName .
"_" . str_pad(strval($dedupTableIndex), 2,
"0", STR_PAD_LEFT);
251 ksort($modelDedupMapping);
252 foreach(Utils::stringifyKeys($dedupMapping) as $blockName => $properties){
254 $dedupMapping[$blockName] = $properties;
257 $model->remappedPropertyValuesIndex = $modelTable;
258 $model->remappedPropertyValues = $modelDedupMapping;
263 $flattenRule->prefix,
264 $flattenRule->flattenedProperty,
265 $flattenRule->suffix,
266 $flattenRule->flattenedValueRemaps,
267 match($flattenRule->flattenedPropertyType){
268 StringTag::class => null,
269 ByteTag::class =>
"byte",
270 IntTag::class =>
"int",
271 default => throw new \LogicException(
"Unexpected tag type " . $flattenRule->flattenedPropertyType .
" in flattened property type")
278 $flattenRule->prefix,
279 $flattenRule->flattenedProperty,
280 $flattenRule->suffix,
281 $flattenRule->flattenedValueRemaps ?? [],
282 match ($flattenRule->flattenedPropertyType) {
283 "string", null => StringTag::class,
284 "int" => IntTag::class,
285 "byte" => ByteTag::class,
286 default => throw new \UnexpectedValueException(
"Unexpected flattened property type $flattenRule->flattenedPropertyType, expected 'string', 'int' or 'byte'")
293 $result->maxVersionMajor = $schema->maxVersionMajor;
294 $result->maxVersionMinor = $schema->maxVersionMinor;
295 $result->maxVersionPatch = $schema->maxVersionPatch;
296 $result->maxVersionRevision = $schema->maxVersionRevision;
298 $result->renamedIds = $schema->renamedIds;
299 ksort($result->renamedIds);
301 $result->renamedProperties = $schema->renamedProperties;
302 ksort($result->renamedProperties);
303 foreach(Utils::stringifyKeys($result->renamedProperties) as $blockName => $properties){
305 $result->renamedProperties[$blockName] = $properties;
308 $result->removedProperties = $schema->removedProperties;
309 ksort($result->removedProperties);
310 foreach(Utils::stringifyKeys($result->removedProperties) as $blockName => $properties){
312 $result->removedProperties[$blockName] = $properties;
315 foreach(Utils::stringifyKeys($schema->addedProperties) as $blockName => $properties){
316 $addedProperties = [];
317 foreach(Utils::stringifyKeys($properties) as $propertyName => $propertyValue){
318 $addedProperties[$propertyName] = self::tagToJsonModel($propertyValue);
320 ksort($addedProperties);
321 $result->addedProperties[$blockName] = $addedProperties;
323 if(isset($result->addedProperties)){
324 ksort($result->addedProperties);
327 self::buildRemappedValuesIndex($schema, $result);
329 foreach(Utils::stringifyKeys($schema->flattenedProperties) as $blockName => $flattenRule){
330 $result->flattenedProperties[$blockName] = self::flattenRuleToJsonModel($flattenRule);
332 if(isset($result->flattenedProperties)){
333 ksort($result->flattenedProperties);
336 foreach(Utils::stringifyKeys($schema->remappedStates) as $oldBlockName => $remaps){
338 foreach($remaps as $remap){
340 array_map(fn(
Tag $tag) => self::tagToJsonModel($tag), $remap->oldState),
341 is_string($remap->newName) ? $remap->newName : self::flattenRuleToJsonModel($remap->newName),
342 array_map(fn(
Tag $tag) => self::tagToJsonModel($tag), $remap->newState),
345 if(count($modelRemap->copiedState) === 0){
346 unset($modelRemap->copiedState);
348 $key = json_encode($modelRemap);
349 assert(!isset($keyedRemaps[$key]));
350 if(isset($keyedRemaps[$key])){
353 $keyedRemaps[$key] = $modelRemap;
357 $filterSizeCompare = count($b->oldState ?? []) <=> count($a->oldState ?? []);
358 if($filterSizeCompare !== 0){
359 return $filterSizeCompare;
362 return json_encode($a->oldState ?? []) <=> json_encode($b->oldState ?? []);
364 $result->remappedStates[$oldBlockName] = array_values($keyedRemaps);
366 if(isset($result->remappedStates)){
367 ksort($result->remappedStates);
378 public static function loadSchemas(
string $path,
int $maxSchemaId) : array{
379 $iterator = new \RegexIterator(
380 new \FilesystemIterator(
382 \FilesystemIterator::KEY_AS_FILENAME | \FilesystemIterator::SKIP_DOTS
384 '/^(\d{4}).*\.json$/',
385 \RegexIterator::GET_MATCH,
386 \RegexIterator::USE_KEY
392 foreach($iterator as $matches){
393 $filename = $matches[0];
394 $schemaId = (int) $matches[1];
396 if($schemaId > $maxSchemaId){
400 $fullPath = Path::join($path, $filename);
402 $raw = Filesystem::fileGetContents($fullPath);
405 $schema = self::loadSchemaFromString($raw, $schemaId);
406 }
catch(\RuntimeException $e){
407 throw new \RuntimeException(
"Loading schema file $fullPath: " . $e->getMessage(), 0, $e);
410 $result[$schemaId] = $schema;
413 ksort($result, SORT_NUMERIC);
417 public static function loadSchemaFromString(
string $raw,
int $schemaId) : BlockStateUpgradeSchema{
419 $json = json_decode($raw, false, flags: JSON_THROW_ON_ERROR);
420 }
catch(\JsonException $e){
421 throw new \RuntimeException($e->getMessage(), 0, $e);
423 if(!is_object($json)){
424 throw new \RuntimeException(
"Unexpected root type of schema file " . gettype($json) .
", expected object");
427 $jsonMapper = new \JsonMapper();
428 $jsonMapper->bExceptionOnMissingData =
true;
429 $jsonMapper->bExceptionOnUndefinedProperty =
true;
430 $jsonMapper->bStrictObjectTypeChecking =
true;
432 $model = $jsonMapper->map($json,
new BlockStateUpgradeSchemaModel());
434 throw new \RuntimeException($e->getMessage(), 0, $e);
437 return self::fromJsonModel($model, $schemaId);