Skip to content

Commit

Permalink
More unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
SandroHc committed Dec 13, 2023
1 parent 3f23b53 commit 84f3496
Show file tree
Hide file tree
Showing 21 changed files with 29,386 additions and 121 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ gradle-app.setting
# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
# gradle/wrapper/gradle-wrapper.properties

# Java Snapshot Testing
/**/__snapshots__/*.debug

/.idea/
/*.iml
Expand Down
10 changes: 9 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,16 @@ dependencies {
implementation 'org.checkerframework:checker-qual:3.41.0'

testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.1'
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.10.1'
testImplementation 'org.assertj:assertj-core:3.24.2'
testImplementation 'io.github.origin-energy:java-snapshot-testing-junit5:4.0.6'
testImplementation 'io.github.origin-energy:java-snapshot-testing-plugin-jackson:4.0.6'
testImplementation 'com.fasterxml.jackson.core:jackson-core:2.16.0'
testImplementation 'com.fasterxml.jackson.core:jackson-databind:2.16.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
testRuntimeOnly 'ch.qos.logback:logback-classic:1.3.14'
testRuntimeOnly 'org.slf4j:slf4j-simple:1.7.36'
testRuntimeOnly 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.16.0'
testRuntimeOnly 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.16.0'
}

java {
Expand Down
132 changes: 109 additions & 23 deletions src/main/java/net/sandrohc/schematic4j/SchematicFormat.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package net.sandrohc.schematic4j;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;

import org.checkerframework.checker.nullness.qual.NonNull;
Expand All @@ -14,8 +19,6 @@
import net.sandrohc.schematic4j.parser.SchematicaParser;
import net.sandrohc.schematic4j.parser.SpongeSchematicParser;

import static net.sandrohc.schematic4j.utils.TagUtils.containsAllTags;

public enum SchematicFormat {
SPONGE_V1 ("schem", SpongeSchematicParser::new),
SPONGE_V2 ("schem", SpongeSchematicParser::new),
Expand Down Expand Up @@ -55,41 +58,124 @@ public Parser createParser() throws NoParserFoundException {
/**
* Tries to guess the schematic format of the input.
*
* @param input The NBT input to check
* @param nbt The NBT input to check
* @return The format guesses from looking at the input, or {@link SchematicFormat#UNKNOWN} if no known format was found.
*/
// TODO: implement candidate system - candidate with most points gets selected
@NonNull
public static SchematicFormat guessFormat(@Nullable NamedTag input) {
if (input == null) {
return SchematicFormat.UNKNOWN;
}
if (!(input.getTag() instanceof CompoundTag)) {
public static @NonNull SchematicFormat guessFormat(@Nullable NamedTag nbt) {
if (nbt == null || !(nbt.getTag() instanceof CompoundTag)) {
return SchematicFormat.UNKNOWN;
}
final CompoundTag rootTag = (CompoundTag) nbt.getTag();

final Candidates<SchematicFormat> candidates = new Candidates<>();

final CompoundTag rootTag = (CompoundTag) input.getTag();
final String rootName = input.getName();
guessSpongeFormat(candidates, nbt, rootTag);
guessSchematicaFormat(candidates, nbt, rootTag);

// Check Sponge Schematic format
if (rootName.equals(SpongeSchematicParser.NBT_ROOT) && containsAllTags(rootTag, "Version", "BlockData", "Palette")) {
return candidates.best().orElse(SchematicFormat.UNKNOWN);
}

private static void guessSpongeFormat(Candidates<SchematicFormat> candidates, @NonNull NamedTag nbt, CompoundTag rootTag) {
if (nbt.getName().equals(SpongeSchematicParser.NBT_ROOT)) {
candidates.increment(SchematicFormat.SPONGE_V1, 1);
candidates.increment(SchematicFormat.SPONGE_V2, 2);
}
if (rootTag.containsKey(SpongeSchematicParser.NBT_VERSION)) {
final int version = rootTag.getInt("Version");
switch (version) {
case 1:
return SchematicFormat.SPONGE_V1;
candidates.increment(SchematicFormat.SPONGE_V1, 1);
case 2:
return SchematicFormat.SPONGE_V2;
default:
log.warn("Found Sponge Schematic with version {}, which is not supported. Using parser for version 2", version);
return SchematicFormat.SPONGE_V2;
candidates.increment(SchematicFormat.SPONGE_V2, 1);
}
} else {
candidates.exclude(SchematicFormat.SPONGE_V1);
candidates.exclude(SchematicFormat.SPONGE_V2);
}
if (rootTag.containsKey(SpongeSchematicParser.NBT_DATA_VERSION)) {
candidates.increment(SchematicFormat.SPONGE_V1, 1);
candidates.increment(SchematicFormat.SPONGE_V2, 1);
} else {
candidates.exclude(SchematicFormat.SPONGE_V2);
}
if (rootTag.containsKey(SpongeSchematicParser.NBT_WIDTH)) {
candidates.increment(SchematicFormat.SPONGE_V1, 1);
candidates.increment(SchematicFormat.SPONGE_V2, 1);
}
if (rootTag.containsKey(SpongeSchematicParser.NBT_HEIGHT)) {
candidates.increment(SchematicFormat.SPONGE_V1, 1);
candidates.increment(SchematicFormat.SPONGE_V2, 1);
}
if (rootTag.containsKey(SpongeSchematicParser.NBT_LENGTH)) {
candidates.increment(SchematicFormat.SPONGE_V1, 1);
candidates.increment(SchematicFormat.SPONGE_V2, 1);
}
if (rootTag.containsKey(SpongeSchematicParser.NBT_PALETTE)) {
candidates.increment(SchematicFormat.SPONGE_V1, 1);
candidates.increment(SchematicFormat.SPONGE_V2, 1);
}
if (rootTag.containsKey(SpongeSchematicParser.NBT_BLOCK_DATA)) {
candidates.increment(SchematicFormat.SPONGE_V1, 1);
candidates.increment(SchematicFormat.SPONGE_V2, 1);
}
}

private static void guessSchematicaFormat(Candidates<SchematicFormat> candidates, @NonNull NamedTag nbt, CompoundTag rootTag) {
if (nbt.getName().equals(SchematicaParser.NBT_ROOT)) {
candidates.increment(SchematicFormat.SCHEMATICA, 1);
}
if (rootTag.containsKey(SchematicaParser.NBT_MAPPING_SCHEMATICA)) {
candidates.increment(SchematicFormat.SCHEMATICA, 10);
} else {
candidates.exclude(SchematicFormat.SCHEMATICA);
}
if (rootTag.containsKey(SchematicaParser.NBT_WIDTH)) {
candidates.increment(SchematicFormat.SCHEMATICA, 1);
} else {
candidates.exclude(SchematicFormat.SCHEMATICA);
}
if (rootTag.containsKey(SchematicaParser.NBT_HEIGHT)) {
candidates.increment(SchematicFormat.SCHEMATICA, 1);
} else {
candidates.exclude(SchematicFormat.SCHEMATICA);
}
if (rootTag.containsKey(SchematicaParser.NBT_LENGTH)) {
candidates.increment(SchematicFormat.SCHEMATICA, 1);
} else {
candidates.exclude(SchematicFormat.SCHEMATICA);
}
}

protected static class Candidates<T> {
protected final Map<T, Integer> candidates = new HashMap<>();
protected final Set<T> excluded = new HashSet<>();

public void increment(T candidate, int weight) {
if (!excluded.contains(candidate)) {
final int currentWeight = candidates.getOrDefault(candidate, 0);
candidates.put(candidate, currentWeight + weight);
log.trace("Format candidate {} prioritized by {} (total: {})", candidate, weight, currentWeight + weight);
}
}

// Check Schematica format
if (rootName.equals(SchematicaParser.NBT_ROOT) && containsAllTags(rootTag, "SchematicaMapping")) {
return SchematicFormat.SCHEMATICA;
public void exclude(T candidate) {
log.trace("Excluded format: {}", candidate);
excluded.add(candidate);
candidates.remove(candidate);
}

return SchematicFormat.UNKNOWN;
public Optional<T> best() {
final Optional<Map.Entry<T, Integer>> best = candidates.entrySet().stream().reduce((a, b) -> {
if (a.getValue() >= b.getValue()) {
return a;
} else {
return b;
}
});
log.trace("Excluded formats: {}", excluded);
log.trace("Format candidates: {}", candidates);
log.trace("Best candidate: {}", best);
return best.map(Map.Entry::getKey);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;

import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
Expand All @@ -18,15 +17,16 @@
import net.sandrohc.schematic4j.schematic.SchematicSchematica.Builder;
import net.sandrohc.schematic4j.schematic.types.*;

import static java.util.stream.Collectors.toMap;
import static net.sandrohc.schematic4j.utils.TagUtils.*;
import static net.sandrohc.schematic4j.utils.TagUtils.getByteArrayOrThrow;

/**
* Parses Schematica files (<i>.schematic</i>).
* <p>
* Specification:<br>
* - https://minecraft.fandom.com/wiki/Schematic_file_format
* - https://github.com/Lunatrius/Schematica/blob/master/src/main/java/com/github/lunatrius/schematica/world/schematic/SchematicAlpha.java
* - <a href="https://minecraft.fandom.com/wiki/Schematic_file_format">https://minecraft.fandom.com/wiki/Schematic_file_format</a>
* - <a href="https://github.com/Lunatrius/Schematica/blob/master/src/main/java/com/github/lunatrius/schematica/world/schematic/SchematicAlpha.java">https://github.com/Lunatrius/Schematica/blob/master/src/main/java/com/github/lunatrius/schematica/world/schematic/SchematicAlpha.java</a>
*/
public class SchematicaParser implements Parser {

Expand All @@ -35,9 +35,6 @@ public class SchematicaParser implements Parser {
public static final String NBT_ROOT = "Schematic";

public static final String NBT_MATERIALS = "Materials";
public static final String NBT_FORMAT_CLASSIC = "Classic";
public static final String NBT_FORMAT_ALPHA = "Alpha";
public static final String NBT_FORMAT_STRUCTURE = "Structure";

public static final String NBT_ICON = "Icon";
public static final String NBT_ICON_ID = "id";
Expand Down Expand Up @@ -82,6 +79,7 @@ public class SchematicaParser implements Parser {
parseBlocks(rootTag, builder);
parseBlockEntities(rootTag, builder);
parseEntities(rootTag, builder);
parseMaterials(rootTag, builder);

return builder.build();
}
Expand Down Expand Up @@ -111,7 +109,7 @@ private void parseBlocks(CompoundTag root, Builder builder) throws ParsingExcept
log.trace("Mapping size: {}", mapping.size());
Map<Integer, SchematicBlock> blocksById = new HashMap<>();
Map<Integer, String> blockNamesById = mapping.entrySet().stream()
.collect(Collectors.toMap(
.collect(toMap(
entry -> ((ShortTag) entry.getValue()).asInt(), // ID
Entry::getKey // Name
));
Expand Down Expand Up @@ -190,7 +188,7 @@ private void parseBlockEntities(CompoundTag root, Builder builder) throws Parsin
!tag.getKey().equals(NBT_TILE_ENTITIES_X) &&
!tag.getKey().equals(NBT_TILE_ENTITIES_Y) &&
!tag.getKey().equals(NBT_TILE_ENTITIES_Z))
.collect(Collectors.toMap(Entry::getKey, e -> unwrap(e.getValue())));
.collect(toMap(Entry::getKey, e -> unwrap(e.getValue()), (a, b) -> b, TreeMap::new));

blockEntities.add(new SchematicBlockEntity(id, SchematicPosInt.from(posX, posY, posZ), extra));
}
Expand Down Expand Up @@ -243,7 +241,7 @@ private void parseEntities(CompoundTag root, Builder builder) throws ParsingExce
!tag.getKey().equals(NBT_ENTITIES_TILE_X) &&
!tag.getKey().equals(NBT_ENTITIES_TILE_Y) &&
!tag.getKey().equals(NBT_ENTITIES_TILE_Z))
.collect(Collectors.toMap(Entry::getKey, e -> unwrap(e.getValue())));
.collect(toMap(Entry::getKey, e -> unwrap(e.getValue()), (a, b) -> b, TreeMap::new));

entities.add(new SchematicEntity(id, pos, extra));
}
Expand All @@ -257,6 +255,11 @@ private void parseEntities(CompoundTag root, Builder builder) throws ParsingExce
builder.entities(entities);
}

private void parseMaterials(CompoundTag root, Builder builder) {
log.trace("Parsing materials");
getString(root, NBT_MATERIALS).ifPresent(builder::materials);
}

@Override
public String toString() {
return "SchematicaParser";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.TreeMap;
import java.util.stream.StreamSupport;

import org.checkerframework.checker.nullness.qual.NonNull;
Expand All @@ -35,6 +35,7 @@
import net.sandrohc.schematic4j.schematic.types.SchematicPosDouble;
import net.sandrohc.schematic4j.schematic.types.SchematicPosInt;

import static java.util.stream.Collectors.toMap;
import static net.sandrohc.schematic4j.utils.DateUtils.epochToDate;
import static net.sandrohc.schematic4j.utils.TagUtils.containsAllTags;
import static net.sandrohc.schematic4j.utils.TagUtils.getByteArrayOrThrow;
Expand Down Expand Up @@ -201,7 +202,7 @@ private void parseBlocks(CompoundTag root, SchematicSponge.Builder builder) thro
final CompoundTag palette = getCompoundOrThrow(root, NBT_PALETTE);
log.trace("Palette size: {}", palette.size());
Map<Integer, SchematicBlock> blockById = palette.entrySet().stream()
.collect(Collectors.toMap(
.collect(toMap(
entry -> ((IntTag) entry.getValue()).asInt(), // ID
entry -> new SchematicBlock(entry.getKey()) // Blockstate
));
Expand All @@ -215,10 +216,6 @@ private void parseBlocks(CompoundTag root, SchematicSponge.Builder builder) thro
byte[] blockDataRaw = getByteArrayOrThrow(root, NBT_BLOCK_DATA);
SchematicBlock[][][] blockData = new SchematicBlock[width][height][length];

int expectedBlocks = width * height * length;
if (blockDataRaw.length != expectedBlocks)
log.warn("Number of blocks does not match expected. Expected {} blocks, but got {}", expectedBlocks, blockDataRaw.length);

// --- Uses code from https://github.com/SpongePowered/Sponge/blob/aa2c8c53b4f9f40297e6a4ee281bee4f4ce7707b/src/main/java/org/spongepowered/common/data/persistence/SchematicTranslator.java#L147-L175
int index = 0;
int i = 0;
Expand Down Expand Up @@ -270,7 +267,7 @@ private void parseBlockEntities(CompoundTag root, Builder builder, int version)
final Map<String, Object> extra = blockEntity.entrySet().stream()
.filter(tag -> !tag.getKey().equals(NBT_ENTITIES_ID) &&
!tag.getKey().equals(NBT_ENTITIES_POS))
.collect(Collectors.toMap(Entry::getKey, e -> unwrap(e.getValue())));
.collect(toMap(Entry::getKey, e -> unwrap(e.getValue()), (a, b) -> b, TreeMap::new));

blockEntities.add(new SchematicBlockEntity(id, SchematicPosInt.from(pos), extra));
}
Expand Down Expand Up @@ -308,7 +305,7 @@ private void parseEntities(CompoundTag root, SchematicSponge.Builder builder) th
final Map<String, Object> extra = entity.entrySet().stream()
.filter(tag -> !tag.getKey().equals(NBT_ENTITIES_ID) &&
!tag.getKey().equals(NBT_ENTITIES_POS))
.collect(Collectors.toMap(Entry::getKey, e -> unwrap(e.getValue())));
.collect(toMap(Entry::getKey, e -> unwrap(e.getValue()), (a, b) -> b, TreeMap::new));

entities.add(new SchematicEntity(id, SchematicPosDouble.from(pos), extra));
}
Expand Down Expand Up @@ -338,7 +335,7 @@ private void parseBiomes(CompoundTag root, SchematicSponge.Builder builder) thro
final CompoundTag palette = getCompoundOrThrow(root, NBT_BIOME_PALETTE);
log.trace("Biome palette size: {}", palette.size());
Map<Integer, SchematicBiome> biomeById = palette.entrySet().stream()
.collect(Collectors.toMap(
.collect(toMap(
entry -> ((IntTag) entry.getValue()).asInt(), // ID
entry -> new SchematicBiome(entry.getKey()) // Blockstate
));
Expand Down
Loading

0 comments on commit 84f3496

Please sign in to comment.