Implement a JSON and a JSON-file renderer. #5
6 changed files with 296 additions and 3 deletions
29
pom.xml
29
pom.xml
|
@ -15,6 +15,7 @@
|
||||||
<url>https://manuel.friedli.info/labyrinth.html</url>
|
<url>https://manuel.friedli.info/labyrinth.html</url>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
|
<jackson.version>2.14.2</jackson.version>
|
||||||
<java.source.version>17</java.source.version>
|
<java.source.version>17</java.source.version>
|
||||||
<java.target.version>17</java.target.version>
|
<java.target.version>17</java.target.version>
|
||||||
<jetbrains-annotations.version>24.0.1</jetbrains-annotations.version>
|
<jetbrains-annotations.version>24.0.1</jetbrains-annotations.version>
|
||||||
|
@ -59,6 +60,16 @@
|
||||||
<artifactId>pdfbox</artifactId>
|
<artifactId>pdfbox</artifactId>
|
||||||
<version>${pdfbox.version}</version>
|
<version>${pdfbox.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-annotations</artifactId>
|
||||||
|
<version>${jackson.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-databind</artifactId>
|
||||||
|
<version>${jackson.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.slf4j</groupId>
|
<groupId>org.slf4j</groupId>
|
||||||
<artifactId>slf4j-api</artifactId>
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
@ -82,6 +93,24 @@
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.jsonschema2pojo</groupId>
|
||||||
|
<artifactId>jsonschema2pojo-maven-plugin</artifactId>
|
||||||
|
<version>1.2.1</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>generate-sources</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>generate</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<sourcePaths>
|
||||||
|
<sourcePath>src/main/resources/labyrinth.schema.json</sourcePath>
|
||||||
|
</sourcePaths>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<artifactId>maven-assembly-plugin</artifactId>
|
<artifactId>maven-assembly-plugin</artifactId>
|
||||||
<configuration>
|
<configuration>
|
||||||
|
|
|
@ -3,23 +3,28 @@ package ch.fritteli.labyrinth.generator;
|
||||||
import ch.fritteli.labyrinth.generator.model.Labyrinth;
|
import ch.fritteli.labyrinth.generator.model.Labyrinth;
|
||||||
import ch.fritteli.labyrinth.generator.renderer.html.HTMLRenderer;
|
import ch.fritteli.labyrinth.generator.renderer.html.HTMLRenderer;
|
||||||
import ch.fritteli.labyrinth.generator.renderer.htmlfile.HTMLFileRenderer;
|
import ch.fritteli.labyrinth.generator.renderer.htmlfile.HTMLFileRenderer;
|
||||||
|
import ch.fritteli.labyrinth.generator.renderer.json.JsonRenderer;
|
||||||
|
import ch.fritteli.labyrinth.generator.renderer.jsonfile.JsonFileRenderer;
|
||||||
import ch.fritteli.labyrinth.generator.renderer.pdffile.PDFFileRenderer;
|
import ch.fritteli.labyrinth.generator.renderer.pdffile.PDFFileRenderer;
|
||||||
import ch.fritteli.labyrinth.generator.renderer.text.TextRenderer;
|
import ch.fritteli.labyrinth.generator.renderer.text.TextRenderer;
|
||||||
import ch.fritteli.labyrinth.generator.renderer.textfile.TextFileRenderer;
|
import ch.fritteli.labyrinth.generator.renderer.textfile.TextFileRenderer;
|
||||||
import lombok.NonNull;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@UtilityClass
|
||||||
public class Main {
|
public class Main {
|
||||||
|
|
||||||
public static void main(@NonNull final String[] args) {
|
public static void main(@NonNull final String[] args) {
|
||||||
final int width = 20;
|
final int width = 20;
|
||||||
final int height = 30;
|
final int height = 30;
|
||||||
final Labyrinth labyrinth = new Labyrinth(width, height/*, 0*/);
|
final Labyrinth labyrinth = new Labyrinth(width, height/*, 0*/);
|
||||||
final TextRenderer textRenderer = TextRenderer.newInstance();
|
final TextRenderer textRenderer = TextRenderer.newInstance();
|
||||||
final HTMLRenderer htmlRenderer = HTMLRenderer.newInstance();
|
final HTMLRenderer htmlRenderer = HTMLRenderer.newInstance();
|
||||||
|
final JsonRenderer jsonRenderer = JsonRenderer.newInstance();
|
||||||
final Path userHome = Paths.get(System.getProperty("user.home"));
|
final Path userHome = Paths.get(System.getProperty("user.home"));
|
||||||
final String baseFilename = getBaseFilename(labyrinth);
|
final String baseFilename = getBaseFilename(labyrinth);
|
||||||
final TextFileRenderer textFileRenderer = TextFileRenderer.newInstance()
|
final TextFileRenderer textFileRenderer = TextFileRenderer.newInstance()
|
||||||
|
@ -27,6 +32,8 @@ public class Main {
|
||||||
.setTargetSolutionFile(userHome.resolve(baseFilename + "-solution.txt"));
|
.setTargetSolutionFile(userHome.resolve(baseFilename + "-solution.txt"));
|
||||||
final HTMLFileRenderer htmlFileRenderer = HTMLFileRenderer.newInstance()
|
final HTMLFileRenderer htmlFileRenderer = HTMLFileRenderer.newInstance()
|
||||||
.setTargetFile(userHome.resolve(baseFilename + ".html"));
|
.setTargetFile(userHome.resolve(baseFilename + ".html"));
|
||||||
|
final JsonFileRenderer jsonFileRenderer = JsonFileRenderer.newInstance()
|
||||||
|
.setTargetFile(userHome.resolve(baseFilename + ".json"));
|
||||||
final PDFFileRenderer pdfFileRenderer = PDFFileRenderer.newInstance()
|
final PDFFileRenderer pdfFileRenderer = PDFFileRenderer.newInstance()
|
||||||
.setTargetFile(userHome.resolve(baseFilename + ".pdf"));
|
.setTargetFile(userHome.resolve(baseFilename + ".pdf"));
|
||||||
|
|
||||||
|
@ -37,10 +44,14 @@ public class Main {
|
||||||
log.info("Text rendering with solution:\n{}", textRenderer.setRenderSolution(true).render(labyrinth));
|
log.info("Text rendering with solution:\n{}", textRenderer.setRenderSolution(true).render(labyrinth));
|
||||||
// Render HTML to stdout
|
// Render HTML to stdout
|
||||||
log.info("HTML rendering:\n{}", htmlRenderer.render(labyrinth));
|
log.info("HTML rendering:\n{}", htmlRenderer.render(labyrinth));
|
||||||
|
// Render JSON to stdout
|
||||||
|
log.info("JSON rendering:\n{}", jsonRenderer.render(labyrinth));
|
||||||
// Render Labyrinth and solution to (separate) files
|
// Render Labyrinth and solution to (separate) files
|
||||||
log.info("Text rendering to file:\n{}", textFileRenderer.render(labyrinth));
|
log.info("Text rendering to file:\n{}", textFileRenderer.render(labyrinth));
|
||||||
// Render HTML to file
|
// Render HTML to file
|
||||||
log.info("HTML rendering to file:\n{}", htmlFileRenderer.render(labyrinth));
|
log.info("HTML rendering to file:\n{}", htmlFileRenderer.render(labyrinth));
|
||||||
|
// Render JSON to file
|
||||||
|
log.info("JSON rendering to file:\n{}", jsonFileRenderer.render(labyrinth));
|
||||||
// Render PDF to file
|
// Render PDF to file
|
||||||
log.info("PDF rendering to file:\n{}", pdfFileRenderer.render(labyrinth));
|
log.info("PDF rendering to file:\n{}", pdfFileRenderer.render(labyrinth));
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
package ch.fritteli.labyrinth.generator.renderer.json;
|
||||||
|
|
||||||
|
import ch.fritteli.labyrinth.generator.json.JsonCell;
|
||||||
|
import ch.fritteli.labyrinth.generator.json.JsonLabyrinth;
|
||||||
|
import ch.fritteli.labyrinth.generator.model.Direction;
|
||||||
|
import ch.fritteli.labyrinth.generator.model.Labyrinth;
|
||||||
|
import ch.fritteli.labyrinth.generator.model.Tile;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
|
||||||
|
class Generator {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final Labyrinth labyrinth;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
JsonLabyrinth generate() {
|
||||||
|
final JsonLabyrinth result = new JsonLabyrinth();
|
||||||
|
result.setId(this.labyrinth.getRandomSeed());
|
||||||
|
result.setWidth(this.labyrinth.getWidth());
|
||||||
|
result.setHeight(this.labyrinth.getHeight());
|
||||||
|
final List<List<JsonCell>> rows = new ArrayList<>();
|
||||||
|
for (int y = 0; y < this.labyrinth.getHeight(); y++) {
|
||||||
|
final ArrayList<JsonCell> row = new ArrayList<>();
|
||||||
|
for (int x = 0; x < this.labyrinth.getWidth(); x++) {
|
||||||
|
// x and y are not effectively final and can therefore not be accessed from within the lambda. Hence, create the string beforehand.
|
||||||
|
final String exceptionString = "Failed to obtain tile at %dx%d, although labyrinth has dimensoins %dx%d"
|
||||||
|
.formatted(x, y, this.labyrinth.getWidth(), this.labyrinth.getHeight());
|
||||||
|
final Tile tile = this.labyrinth.getTileAt(x, y)
|
||||||
|
.getOrElseThrow(() -> new IllegalStateException(exceptionString));
|
||||||
|
final JsonCell cell = new JsonCell();
|
||||||
|
cell.setTop(tile.hasWallAt(Direction.TOP));
|
||||||
|
cell.setRight(tile.hasWallAt(Direction.RIGHT));
|
||||||
|
cell.setBottom(tile.hasWallAt(Direction.BOTTOM));
|
||||||
|
cell.setLeft(tile.hasWallAt(Direction.LEFT));
|
||||||
|
cell.setSolution(tile.isSolution());
|
||||||
|
row.add(cell);
|
||||||
|
}
|
||||||
|
rows.add(row);
|
||||||
|
}
|
||||||
|
result.setGrid(rows);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
package ch.fritteli.labyrinth.generator.renderer.json;
|
||||||
|
|
||||||
|
import ch.fritteli.labyrinth.generator.json.JsonCell;
|
||||||
|
import ch.fritteli.labyrinth.generator.json.JsonLabyrinth;
|
||||||
|
import ch.fritteli.labyrinth.generator.model.Labyrinth;
|
||||||
|
import ch.fritteli.labyrinth.generator.renderer.Renderer;
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.NonNull;
|
||||||
|
|
||||||
|
public class JsonRenderer implements Renderer<String> {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
private JsonRenderer() {
|
||||||
|
this.objectMapper = new ObjectMapper()
|
||||||
|
.enable(SerializationFeature.INDENT_OUTPUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static JsonRenderer newInstance() {
|
||||||
|
return new JsonRenderer();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static JsonLabyrinth createSingleCellLabyrinth() {
|
||||||
|
// This is the only cell.
|
||||||
|
final JsonCell cell = new JsonCell();
|
||||||
|
cell.setRight(true);
|
||||||
|
cell.setLeft(true);
|
||||||
|
cell.setSolution(true);
|
||||||
|
// Wrap that in a nested list.
|
||||||
|
final List<List<JsonCell>> rows = new ArrayList<>();
|
||||||
|
rows.add(new ArrayList<>());
|
||||||
|
rows.get(0).add(cell);
|
||||||
|
// Wrap it all in an instance of JsonLabyrinth.
|
||||||
|
final JsonLabyrinth jsonLabyrinth = new JsonLabyrinth();
|
||||||
|
jsonLabyrinth.setId(0L);
|
||||||
|
jsonLabyrinth.setGrid(rows);
|
||||||
|
return jsonLabyrinth;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private String toString(@NonNull final JsonLabyrinth jsonLabyrinth) {
|
||||||
|
try {
|
||||||
|
return this.objectMapper.writeValueAsString(jsonLabyrinth);
|
||||||
|
} catch (final JsonProcessingException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String render(@NonNull final Labyrinth labyrinth) {
|
||||||
|
final JsonLabyrinth jsonLabyrinth;
|
||||||
|
if (labyrinth.getWidth() == 0 || labyrinth.getHeight() == 0) {
|
||||||
|
jsonLabyrinth = createSingleCellLabyrinth();
|
||||||
|
} else {
|
||||||
|
final Generator generator = new Generator(labyrinth);
|
||||||
|
jsonLabyrinth = generator.generate();
|
||||||
|
}
|
||||||
|
return toString(jsonLabyrinth);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
package ch.fritteli.labyrinth.generator.renderer.jsonfile;
|
||||||
|
|
||||||
|
import ch.fritteli.labyrinth.generator.model.Labyrinth;
|
||||||
|
import ch.fritteli.labyrinth.generator.renderer.Renderer;
|
||||||
|
import ch.fritteli.labyrinth.generator.renderer.json.JsonRenderer;
|
||||||
|
import io.vavr.control.Option;
|
||||||
|
import io.vavr.control.Try;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class JsonFileRenderer implements Renderer<Path> {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static final JsonRenderer JSON_RENDERER = JsonRenderer.newInstance();
|
||||||
|
@NonNull
|
||||||
|
private Option<Path> targetFile;
|
||||||
|
|
||||||
|
private JsonFileRenderer() {
|
||||||
|
this.targetFile = Try
|
||||||
|
.of(() -> Files.createTempFile("labyrinth_", ".json"))
|
||||||
|
.onFailure(ex -> log.error("Unable to set default target file.", ex))
|
||||||
|
.toOption();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static JsonFileRenderer newInstance() {
|
||||||
|
return new JsonFileRenderer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isTargetFileDefinedAndWritable() {
|
||||||
|
return this.targetFile
|
||||||
|
.map(Path::toFile)
|
||||||
|
.exists(File::canWrite);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public JsonFileRenderer setTargetFile(@NonNull final Path targetFile) {
|
||||||
|
this.targetFile = Option.of(targetFile);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Path render(@NonNull final Labyrinth labyrinth) {
|
||||||
|
if (!this.isTargetFileDefinedAndWritable()) {
|
||||||
|
try {
|
||||||
|
Files.createFile(this.targetFile.get());
|
||||||
|
} catch (final IOException | NoSuchElementException e) {
|
||||||
|
throw new IllegalArgumentException("Cannot write to target file.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final String json = JSON_RENDERER.render(labyrinth);
|
||||||
|
final Path outputFile = this.targetFile.get();
|
||||||
|
try {
|
||||||
|
Files.writeString(outputFile, json, StandardCharsets.UTF_8);
|
||||||
|
} catch (final IOException e) {
|
||||||
|
log.error("Failed writing to file %s".formatted(outputFile.normalize()), e);
|
||||||
|
}
|
||||||
|
return outputFile;
|
||||||
|
}
|
||||||
|
}
|
69
src/main/resources/labyrinth.schema.json
Normal file
69
src/main/resources/labyrinth.schema.json
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://manuel.friedli.info/labyrinth-1/labyrinth.schema.json",
|
||||||
|
"javaType": "ch.fritteli.labyrinth.generator.json.JsonLabyrinth",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"width",
|
||||||
|
"height",
|
||||||
|
"grid"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"existingJavaType": "java.lang.Long"
|
||||||
|
},
|
||||||
|
"width": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1
|
||||||
|
},
|
||||||
|
"height": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1
|
||||||
|
},
|
||||||
|
"grid": {
|
||||||
|
"$ref": "#/$defs/grid"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"$defs": {
|
||||||
|
"grid": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/$defs/row"
|
||||||
|
},
|
||||||
|
"minItems": 1
|
||||||
|
},
|
||||||
|
"row": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/$defs/cell"
|
||||||
|
},
|
||||||
|
"minItems": 1
|
||||||
|
},
|
||||||
|
"cell": {
|
||||||
|
"type": "object",
|
||||||
|
"javaType": "ch.fritteli.labyrinth.generator.json.JsonCell",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [],
|
||||||
|
"properties": {
|
||||||
|
"top": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"right": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"bottom": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"left": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"solution": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue