From e800afc3ab60f1859439158f83e09f420a70709d Mon Sep 17 00:00:00 2001 From: TechnicJelle <22576047+TechnicJelle@users.noreply.github.com> Date: Tue, 5 Mar 2024 00:00:50 +0100 Subject: [PATCH] Handle and Log issues better --- .../common/ChunkClass.java | 18 ++-- .../bluemapsignextractor/common/Core.java | 2 +- .../common/MCARegion.java | 87 +++++++++++++------ src/test/java/LoadRegionsTest.java | 45 +++++----- src/test/java/mockery/ConsoleLogger.java | 42 +++++++++ 5 files changed, 133 insertions(+), 61 deletions(-) create mode 100644 src/test/java/mockery/ConsoleLogger.java diff --git a/src/main/java/com/technicjelle/bluemapsignextractor/common/ChunkClass.java b/src/main/java/com/technicjelle/bluemapsignextractor/common/ChunkClass.java index 183d79f..3aa30cf 100644 --- a/src/main/java/com/technicjelle/bluemapsignextractor/common/ChunkClass.java +++ b/src/main/java/com/technicjelle/bluemapsignextractor/common/ChunkClass.java @@ -6,7 +6,7 @@ import com.technicjelle.bluemapsignextractor.versions.MC_1_18_2.MC_1_18_2_Chunk; import com.technicjelle.bluemapsignextractor.versions.MC_1_20_4.MC_1_20_4_Chunk; -import java.io.IOException; +import java.io.UnsupportedEncodingException; public class ChunkClass { private final Class javaType; @@ -21,20 +21,12 @@ public Class getJavaType() { return javaType; } - public int getDataVersion() { - return dataVersion; - } - - public String getTypeName() { - return javaType.getSimpleName(); - } - @Override public String toString() { - return "ChunkClass { DataVersion: " + dataVersion + " -> Loader: " + getTypeName() + " }"; + return "ChunkClass { DataVersion: " + dataVersion + " -> Loader: " + javaType.getSimpleName() + " }"; } - public static ChunkClass getFromDataVersion(int dataVersion) throws IOException { + public static ChunkClass createFromDataVersion(int dataVersion) throws UnsupportedEncodingException { //https://minecraft.wiki/w/Data_version#List_of_data_versions if (dataVersion >= 3463) { return new ChunkClass(MC_1_20_4_Chunk.class, dataVersion); @@ -56,9 +48,9 @@ public static ChunkClass getFromDataVersion(int dataVersion) throws IOException } else if (intInRange(dataVersion, 1444, 1631)) { return new ChunkClass(MC_1_13_2_Chunk.class, dataVersion); } else if (dataVersion < 1444) { - throw new IOException("Chunk DataVersion (" + dataVersion + ") is too old! Please upgrade your chunks to at least 1.13.2"); + throw new UnsupportedEncodingException("Chunk DataVersion (" + dataVersion + ") is too old! Please upgrade your chunks to at least 1.13.2."); } else { - throw new IOException("Unsupported Chunk DataVersion: " + dataVersion); + throw new UnsupportedEncodingException("Unsupported Chunk DataVersion: " + dataVersion + "."); } } diff --git a/src/main/java/com/technicjelle/bluemapsignextractor/common/Core.java b/src/main/java/com/technicjelle/bluemapsignextractor/common/Core.java index 60b430d..2f63dfa 100644 --- a/src/main/java/com/technicjelle/bluemapsignextractor/common/Core.java +++ b/src/main/java/com/technicjelle/bluemapsignextractor/common/Core.java @@ -52,7 +52,7 @@ public static MarkerSet loadMarkerSetFromWorld(Logger logger, Path regionFolder) private static void fillMarkerSetFromRegionFile(Logger logger, MarkerSet markerSet, Path regionFile) { logger.fine("Processing region " + regionFile.getFileName().toString()); - final MCARegion mcaRegion = new MCARegion(regionFile); + final MCARegion mcaRegion = new MCARegion(logger, regionFile); try { for (BlockEntity blockEntity : mcaRegion.getBlockEntities()) { if (blockEntity.isInvalidSign()) continue; diff --git a/src/main/java/com/technicjelle/bluemapsignextractor/common/MCARegion.java b/src/main/java/com/technicjelle/bluemapsignextractor/common/MCARegion.java index c07d918..4fda5eb 100644 --- a/src/main/java/com/technicjelle/bluemapsignextractor/common/MCARegion.java +++ b/src/main/java/com/technicjelle/bluemapsignextractor/common/MCARegion.java @@ -30,6 +30,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.ReadableByteChannel; @@ -39,6 +40,8 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.logging.Level; +import java.util.logging.Logger; public class MCARegion { public static final String FILE_SUFFIX = ".mca"; @@ -46,9 +49,15 @@ public class MCARegion { private static final BlueNBT nbt = new BlueNBT(); private final Path regionFile; + private final Logger logger; - public MCARegion(Path regionFile) { + public MCARegion(Logger logger, Path regionFile) { this.regionFile = regionFile; + this.logger = logger; + } + + private void logDebugMessage(int x, int z, Level level, String errorMessage) { + logger.log(level, "Problem in chunk at " + padLeft(x) + ", " + padLeft(z) + " in region file " + regionFile.toAbsolutePath() + "\n" + errorMessage); } public Collection getBlockEntities() throws IOException { @@ -86,42 +95,68 @@ public Collection getBlockEntities() throws IOException { channel.position(offset); readFully(channel, chunkDataBuffer, 0, size); + final Compression compression; + try { + compression = concludeCompression(chunkDataBuffer); + } catch (UnsupportedEncodingException e) { + logDebugMessage(x, z, Level.SEVERE, e.getMessage()); + continue; + } + if (chunkClass == null) { //Starts off as null. This is the first chunk we're loading. - final ChunkWithVersion chunkWithVersion = loadChunk(ChunkWithVersion.class, chunkDataBuffer, size); + final ChunkWithVersion chunkWithVersion = loadChunk(ChunkWithVersion.class, compression, chunkDataBuffer, size); if (chunkWithVersion == null) { - throw new IOException("Failed to conclude ChunkClass from chunk at " + padLeft(x) + ", " + padLeft(z) + " in region file " + regionFile.toAbsolutePath()); + logDebugMessage(x, z, Level.SEVERE, "Failed to conclude ChunkClass. Skipping..."); + continue; + } + try { + chunkClass = ChunkClass.createFromDataVersion(chunkWithVersion.getDataVersion()); + } catch (UnsupportedEncodingException e) { + logDebugMessage(x, z, Level.WARNING, e.getMessage() + " Skipping..."); + continue; } - chunkClass = ChunkClass.getFromDataVersion(chunkWithVersion.getDataVersion()); } - Chunk chunk = loadChunk(chunkClass.getJavaType(), chunkDataBuffer, size); - final ChunkClass newChunkClass = ChunkClass.getFromDataVersion(chunk.getDataVersion()); + Chunk chunk = loadChunk(chunkClass.getJavaType(), compression, chunkDataBuffer, size); + final ChunkClass newChunkClass; + try { + newChunkClass = ChunkClass.createFromDataVersion(chunk.getDataVersion()); + } catch (UnsupportedEncodingException e) { + logDebugMessage(x, z, Level.WARNING, e.getMessage() + " Skipping..."); + continue; + } //Check if current chunk needs a different loader than the previous chunk if (newChunkClass.getJavaType() != chunkClass.getJavaType()) { -// System.out.println("Chunk at " + padLeft(x) + ", " + padLeft(z) + " has a significantly different data version (" + newChunkClass.getDataVersion() + ") " + -// "than the previous chunk (" + chunkClass.getDataVersion() + ") in this region file.\n" + -// "\tSwitching loader from " + chunkClass.getTypeName() + " to " + newChunkClass.getTypeName() + "..."); + logDebugMessage(x, z, Level.FINE, "Significantly different data versions between previous and next chunks.\n" + + "\tPrevious: " + chunkClass + "\n" + + "\tNext: " + newChunkClass + "\n" + + "\tSwitching loader..."); chunkClass = newChunkClass; //Load chunk again, with the new class - chunk = loadChunk(chunkClass.getJavaType(), chunkDataBuffer, size); + chunk = loadChunk(chunkClass.getJavaType(), compression, chunkDataBuffer, size); } -// System.out.println("Chunk at " + x + ", " + z + ": " + chunkClass); + logger.log(Level.FINEST, "Chunk at " + x + ", " + z + ": " + chunkClass); try { - if (!chunk.isGenerated()) continue; + if (!chunk.isGenerated()) { + logDebugMessage(x, z, Level.FINER, "Chunk is not fully generated, yet. Skipping..."); + continue; + } } catch (NullPointerException e) { - throw new IOException("NullPointerException in chunk " + padLeft(x) + ", " + padLeft(z) + " in region file " + regionFile.toAbsolutePath() + "\n" + - "\t\tChunk class: " + chunkClass, e); + logDebugMessage(x, z, Level.SEVERE, "Failed to conclude ChunkClass due to a NullPointerException in the isGenerated() call. Skipping...\n" + + "\tChunk class: " + chunkClass); + continue; } BlockEntity[] chunkBlockEntities = chunk.getBlockEntities(); if (chunkBlockEntities == null) { - throw new IOException("Chunk's BlockEntities was null in chunk " + padLeft(x) + ", " + padLeft(z) + " in region file " + regionFile.toAbsolutePath() + "\n" + - "\t\tChunk class: " + chunkClass + "\n" + - "\t\tChunk generation status: " + chunk.getStatus() + "\n" + - "\t\tChunk is generated: " + chunk.isGenerated()); + logDebugMessage(x, z, Level.SEVERE, "Chunk's BlockEntities was null. Skipping...\n" + + "\tChunk class: " + chunkClass + "\n" + + "\tChunk generation status: " + chunk.getStatus() + "\n" + + "\tChunk is generated: " + chunk.isGenerated()); + continue; } Collections.addAll(regionBlockEntities, chunkBlockEntities); @@ -138,24 +173,22 @@ public static String padLeft(int i) { return String.format(format, i); } - private static T loadChunk(Class type, byte[] data, int size) throws IOException { + private static Compression concludeCompression(byte[] data) throws UnsupportedEncodingException { int compressionTypeId = data[4]; - Compression compression; switch (compressionTypeId) { case 0: case 3: - compression = Compression.NONE; - break; + return Compression.NONE; case 1: - compression = Compression.GZIP; - break; + return Compression.GZIP; case 2: - compression = Compression.DEFLATE; - break; + return Compression.DEFLATE; default: - throw new IOException("Unknown chunk compression-id: " + compressionTypeId); + throw new UnsupportedEncodingException("Unknown chunk compression-id: " + compressionTypeId); } + } + private static T loadChunk(Class type, Compression compression, byte[] data, int size) throws IOException { try (InputStream in = new BufferedInputStream(compression.decompress(new ByteArrayInputStream(data, 5, size - 5)))) { return loadChunk(type, in); } diff --git a/src/test/java/LoadRegionsTest.java b/src/test/java/LoadRegionsTest.java index 8f2b960..886c118 100644 --- a/src/test/java/LoadRegionsTest.java +++ b/src/test/java/LoadRegionsTest.java @@ -2,20 +2,19 @@ import com.technicjelle.bluemapsignextractor.common.Core; import com.technicjelle.bluemapsignextractor.common.MCARegion; import de.bluecolored.bluemap.api.markers.MarkerSet; -import org.jetbrains.annotations.NotNull; +import mockery.ConsoleLogger; import org.jetbrains.annotations.Nullable; +import org.junit.Assert; import org.junit.Test; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Stream; -import static org.junit.Assert.assertEquals; - -@SuppressWarnings("CallToPrintStackTrace") public class LoadRegionsTest { @Test public void test_MC_1_13_2() { @@ -101,11 +100,12 @@ public void test_MC_1_20_4_SignWithCustomJSON() { @Test public void test_MC_1_20_4_LoadFullMarkerSet() { Path regionFolder = getTestResource("MC_1_20_4/region_flat_world"); - MarkerSet markerSet = Core.loadMarkerSetFromWorld(Logger.getLogger("test"), regionFolder); + Logger logger = ConsoleLogger.createLogger(regionFolder.toAbsolutePath().toString(), Level.FINE); + MarkerSet markerSet = Core.loadMarkerSetFromWorld(logger, regionFolder); - System.out.println(markerSet); - markerSet.getMarkers().forEach((key, marker) -> System.out.println(key + " -> " + marker.getLabel())); - assertEquals(1, markerSet.getMarkers().size()); + logger.log(Level.INFO, "MarkerSet \"" + markerSet.getLabel() + "\" contains " + markerSet.getMarkers().size() + " markers:"); + markerSet.getMarkers().forEach((key, marker) -> logger.log(Level.INFO, key + " -> " + marker.getLabel())); + Assert.assertEquals(1, markerSet.getMarkers().size()); } @Test @@ -128,6 +128,9 @@ public void test_MC_1_20_4_DifferentDataVersions() { /// Helper methods /// /// -------------- /// + + // --- Public --- // + public static Path getTestResource(String resourcePath) { return Paths.get("").resolve("src/test/resources/" + resourcePath); } @@ -136,13 +139,13 @@ public static Path getTestResource(String resourcePath) { * @param regionFolderName The name of the folder in src/test/resources to test the region files in */ public static void testRegionFolder(final String regionFolderName) { + Logger logger = ConsoleLogger.createLogger(regionFolderName, Level.FINE); Path regionFolder = getTestResource(regionFolderName); assert Files.exists(regionFolder); try (final Stream stream = Files.list(regionFolder)) { - stream.filter(path -> path.toString().endsWith(MCARegion.FILE_SUFFIX)).forEach(resourcePath -> testMCAFile(resourcePath, null)); + stream.filter(path -> path.toString().endsWith(MCARegion.FILE_SUFFIX)).forEach(resourcePath -> testMCAFile(logger, resourcePath, null)); } catch (IOException e) { - System.err.println("Error reading region folder:"); - e.printStackTrace(); + logger.log(Level.SEVERE, "Error reading region folder", e); } } @@ -151,17 +154,20 @@ public static void testRegionFolder(final String regionFolderName) { * @param expectedAmountOfSigns The amount of signs to expect in the region file. If null, the expected amount of signs will not be checked. */ public static void testMCAFile(String resourcePath, @Nullable Integer expectedAmountOfSigns) { + Logger logger = ConsoleLogger.createLogger(resourcePath, Level.FINE); Path regionFile = getTestResource(resourcePath); - testMCAFile(regionFile, expectedAmountOfSigns); + testMCAFile(logger, regionFile, expectedAmountOfSigns); } + // --- Private --- // + /** * @param regionFile The region file to test * @param expectedAmountOfSigns The amount of signs to expect in the region file. If null, the expected amount of signs will not be checked. */ - public static void testMCAFile(@NotNull Path regionFile, @Nullable Integer expectedAmountOfSigns) { - System.out.println("Processing region " + regionFile.getFileName().toString()); - final MCARegion mcaRegion = new MCARegion(regionFile); + private static void testMCAFile(Logger logger, Path regionFile, @Nullable Integer expectedAmountOfSigns) { + logger.log(Level.INFO, "Processing region " + regionFile.getFileName().toString()); + final MCARegion mcaRegion = new MCARegion(logger, regionFile); int signsFound = 0; try { @@ -170,7 +176,7 @@ public static void testMCAFile(@NotNull Path regionFile, @Nullable Integer expec signsFound++; - System.out.println(blockEntity.getClass().getSimpleName() + ":\n" + + logger.log(Level.CONFIG, blockEntity.getClass().getSimpleName() + ":\n" + "Key: " + blockEntity.createKey() + "\n" + "Label: " + blockEntity.getLabel() + "\n" + "Position: " + blockEntity.getPosition() + "\n" + @@ -178,13 +184,12 @@ public static void testMCAFile(@NotNull Path regionFile, @Nullable Integer expec "\n\n"); } } catch (IOException e) { - System.err.println("Error reading region file:"); - e.printStackTrace(); + logger.log(Level.SEVERE, "Error reading region file", e); } if (expectedAmountOfSigns != null) - assertEquals(expectedAmountOfSigns.intValue(), signsFound); + Assert.assertEquals(expectedAmountOfSigns.intValue(), signsFound); - System.out.println("Successfully processed region file, and found " + signsFound + " signs\n"); + logger.log(Level.INFO, "Successfully processed region file, and found " + signsFound + " signs\n"); } } diff --git a/src/test/java/mockery/ConsoleLogger.java b/src/test/java/mockery/ConsoleLogger.java new file mode 100644 index 0000000..610e1f0 --- /dev/null +++ b/src/test/java/mockery/ConsoleLogger.java @@ -0,0 +1,42 @@ +package mockery; + +import java.util.logging.ConsoleHandler; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; +import java.util.logging.SimpleFormatter; +import java.util.logging.StreamHandler; + +public class ConsoleLogger extends StreamHandler { + private static String formatLog(LogRecord lr) { + return String.format("[%1$-7s] %2$s%n", lr.getLevel().getName(), lr.getMessage()); + } + + public static Logger createLogger(String name, Level minimumLogLevel) { + Logger logger = Logger.getLogger(name); + logger.setUseParentHandlers(false); + logger.setLevel(minimumLogLevel); + + ConsoleHandler warning2stderrLogger = new ConsoleHandler(); + warning2stderrLogger.setLevel(Level.WARNING); + warning2stderrLogger.setFormatter(new SimpleFormatter() { + @Override + public synchronized String format(LogRecord lr) { + return formatLog(lr); + } + }); + logger.addHandler(warning2stderrLogger); + + ConsoleLogger fine2stdoutLogger = new ConsoleLogger(); + logger.addHandler(fine2stdoutLogger); + + return logger; + } + + @Override + public void publish(LogRecord record) { + final Level level = record.getLevel(); + if (level.intValue() <= Level.INFO.intValue()) + System.out.print(formatLog(record)); + } +}