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…
	
	Add table
		Add a link
		
	
		Reference in a new issue