Skip to content

Commit

Permalink
Handle and Log issues better
Browse files Browse the repository at this point in the history
  • Loading branch information
TechnicJelle committed Mar 4, 2024
1 parent eec1673 commit e800afc
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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<? extends Chunk> javaType;
Expand All @@ -21,20 +21,12 @@ public Class<? extends Chunk> 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);
Expand All @@ -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 + ".");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -39,16 +40,24 @@
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";

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<BlockEntity> getBlockEntities() throws IOException {
Expand Down Expand Up @@ -86,42 +95,68 @@ public Collection<BlockEntity> 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);
Expand All @@ -138,24 +173,22 @@ public static String padLeft(int i) {
return String.format(format, i);
}

private static <T> T loadChunk(Class<T> 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> T loadChunk(Class<T> 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);
}
Expand Down
45 changes: 25 additions & 20 deletions src/test/java/LoadRegionsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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
Expand All @@ -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);
}
Expand All @@ -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<Path> 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);
}
}

Expand All @@ -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 {
Expand All @@ -170,21 +176,20 @@ 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" +
blockEntity.getFormattedHTML() +
"\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");
}
}
42 changes: 42 additions & 0 deletions src/test/java/mockery/ConsoleLogger.java
Original file line number Diff line number Diff line change
@@ -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));
}
}

0 comments on commit e800afc

Please sign in to comment.