Merge pull request 'Implement a JSON and a JSON-file renderer.' (#5) from feature/json-renderer into master
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: java/labyrinth-generator#5
This commit is contained in:
commit
f75e068c18
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>
|
||||
|
||||
<properties>
|
||||
<jackson.version>2.14.2</jackson.version>
|
||||
<java.source.version>17</java.source.version>
|
||||
<java.target.version>17</java.target.version>
|
||||
<jetbrains-annotations.version>24.0.1</jetbrains-annotations.version>
|
||||
|
@ -59,6 +60,16 @@
|
|||
<artifactId>pdfbox</artifactId>
|
||||
<version>${pdfbox.version}</version>
|
||||
</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>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
|
@ -82,6 +93,24 @@
|
|||
</dependencies>
|
||||
<build>
|
||||
<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>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<configuration>
|
||||
|
|
|
@ -3,23 +3,28 @@ package ch.fritteli.labyrinth.generator;
|
|||
import ch.fritteli.labyrinth.generator.model.Labyrinth;
|
||||
import ch.fritteli.labyrinth.generator.renderer.html.HTMLRenderer;
|
||||
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.text.TextRenderer;
|
||||
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.Paths;
|
||||
import lombok.NonNull;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@UtilityClass
|
||||
public class Main {
|
||||
|
||||
public static void main(@NonNull final String[] args) {
|
||||
final int width = 20;
|
||||
final int height = 30;
|
||||
final Labyrinth labyrinth = new Labyrinth(width, height/*, 0*/);
|
||||
final TextRenderer textRenderer = TextRenderer.newInstance();
|
||||
final HTMLRenderer htmlRenderer = HTMLRenderer.newInstance();
|
||||
final JsonRenderer jsonRenderer = JsonRenderer.newInstance();
|
||||
final Path userHome = Paths.get(System.getProperty("user.home"));
|
||||
final String baseFilename = getBaseFilename(labyrinth);
|
||||
final TextFileRenderer textFileRenderer = TextFileRenderer.newInstance()
|
||||
|
@ -27,6 +32,8 @@ public class Main {
|
|||
.setTargetSolutionFile(userHome.resolve(baseFilename + "-solution.txt"));
|
||||
final HTMLFileRenderer htmlFileRenderer = HTMLFileRenderer.newInstance()
|
||||
.setTargetFile(userHome.resolve(baseFilename + ".html"));
|
||||
final JsonFileRenderer jsonFileRenderer = JsonFileRenderer.newInstance()
|
||||
.setTargetFile(userHome.resolve(baseFilename + ".json"));
|
||||
final PDFFileRenderer pdfFileRenderer = PDFFileRenderer.newInstance()
|
||||
.setTargetFile(userHome.resolve(baseFilename + ".pdf"));
|
||||
|
||||
|
@ -37,10 +44,14 @@ public class Main {
|
|||
log.info("Text rendering with solution:\n{}", textRenderer.setRenderSolution(true).render(labyrinth));
|
||||
// Render HTML to stdout
|
||||
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
|
||||
log.info("Text rendering to file:\n{}", textFileRenderer.render(labyrinth));
|
||||
// Render HTML to file
|
||||
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
|
||||
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