Implement a JSON and a JSON-file renderer.
This commit is contained in:
		
							parent
							
								
									81b5d7adac
								
							
						
					
					
						commit
						715d89a57a
					
				
					 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…
	
	Add table
		Add a link
		
	
		Reference in a new issue