1 package io.github.robertograham.rleparser;
2
3 import io.github.robertograham.rleparser.domain.*;
4 import io.github.robertograham.rleparser.domain.enumeration.Status;
5 import io.github.robertograham.rleparser.helper.FileHelper;
6 import io.github.robertograham.rleparser.helper.RleFileHelper;
7 import io.github.robertograham.rleparser.helper.StatusRunHelper;
8 //import org.intellij.lang.annotations.Language;
9
10 import java.io.BufferedReader;
11 import java.io.InputStream;
12 import java.io.InputStreamReader;
13 import java.net.URI;
14 import java.util.*;
15 import java.util.regex.Matcher;
16 import java.util.regex.Pattern;
17 import java.util.stream.Collectors;
18
19 public class RleParser {
20
21 private static final String META_DATA_WIDTH_KEY = "x";
22 private static final String META_DATA_HEIGHT_KEY = "y";
23 private static final String META_DATA_RULE_KEY = "rule";
24 private static final String ENCODED_CELL_DATA_TERMINATOR = "!";
25 //@Language("RegExp")
26 private static final String ENCODED_CELL_DATA_LINE_SEPARATOR_REGEX = "\\$";
27 // @Language("RegExp")
28 private static final String ENCODED_RUN_LENGTH_STATUS_REGEX = "\\d*[a-z$]";
29 private static final Pattern ENCODED_STATUS_RUN_PATTERN = Pattern.compile(ENCODED_RUN_LENGTH_STATUS_REGEX);
30
31 public static PatternData readPatternData(URI rleFileUri) {
32 List<String> lines = FileHelper.getFileAsStringList(rleFileUri);
33
34 List<String> trimmedNonEmptyNonCommentLines = lines.stream()
35 .filter(Objects::nonNull)
36 .map(String::trim)
37 .filter(string -> !string.isEmpty())
38 .filter(string -> !RleFileHelper.isCommentString(string))
39 .collect(Collectors.toList());
40
41 if (trimmedNonEmptyNonCommentLines.size() == 0)
42 throw new IllegalArgumentException("No non-comment, non-empty lines in RLE file");
43
44 MetaData metaData = readMetaData(trimmedNonEmptyNonCommentLines.get(0));
45 LiveCells liveCells = extractLiveCells(
46 metaData,
47 trimmedNonEmptyNonCommentLines.stream()
48 .skip(1)
49 .collect(Collectors.joining())
50 );
51
52 return new PatternData(metaData, liveCells);
53 }
54 public static PatternData readPatternData(InputStream inputStream) {
55 InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
56 BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
57 List<String> trimmedNonEmptyNonCommentLines= bufferedReader.lines()
58 .filter(Objects::nonNull)
59 .map(String::trim)
60 .filter(string -> !string.isEmpty())
61 .filter(string -> !RleFileHelper.isCommentString(string))
62 .collect(Collectors.toList());
63
64 if (trimmedNonEmptyNonCommentLines.size() == 0)
65 throw new IllegalArgumentException("No non-comment, non-empty lines in RLE file");
66
67 MetaData metaData = readMetaData(trimmedNonEmptyNonCommentLines.get(0));
68 LiveCells liveCells = extractLiveCells(
69 metaData,
70 trimmedNonEmptyNonCommentLines.stream()
71 .skip(1)
72 .collect(Collectors.joining())
73 );
74
75 return new PatternData(metaData, liveCells);
76 }
77 private static MetaData readMetaData(String line) {
78 String lineWithoutWhiteSpace = line.replaceAll("\\s", "");
79 String[] metaDataProperties = lineWithoutWhiteSpace.split(",");
80
81 if (metaDataProperties.length < 2)
82 throw new IllegalArgumentException("RLE file header has less than two properties");
83
84 Map<String, String> metaDataPropertyKeyValueMap = Arrays.stream(metaDataProperties)
85 .map(metaDataProperty -> metaDataProperty.split("="))
86 .map(metaDataPropertyKeyValueArray -> {
87 if (metaDataPropertyKeyValueArray.length < 2)
88 return new String[]{metaDataPropertyKeyValueArray[0], null};
89 else
90 return metaDataPropertyKeyValueArray;
91 })
92 .collect(Collectors.toMap(metaDataPropertyKeyValueArray -> metaDataPropertyKeyValueArray[0], metaDataPropertyKeyValueArray -> metaDataPropertyKeyValueArray[1]));
93
94 String width = metaDataPropertyKeyValueMap.get(META_DATA_WIDTH_KEY);
95 String height = metaDataPropertyKeyValueMap.get(META_DATA_HEIGHT_KEY);
96 String rule = metaDataPropertyKeyValueMap.get(META_DATA_RULE_KEY);
97
98 return new MetaData(Integer.parseInt(width), Integer.parseInt(height), rule);
99 }
100
101 private static LiveCells extractLiveCells(MetaData metaData, String rleCellData) {
102 if (metaData.getWidth() == 0 && metaData.getHeight() == 0)
103 if (rleCellData.length() > 0)
104 throw new IllegalArgumentException("RLE header has width 0 and height 0 but there are lines after it");
105 else
106 return new LiveCells(new HashSet<>());
107 else if (rleCellData.length() == 0)
108 throw new IllegalArgumentException("RLE header has width > 0 and height > 0 but there are no lines after it");
109 else if (!rleCellData.contains(ENCODED_CELL_DATA_TERMINATOR))
110 throw new IllegalArgumentException("RLE pattern did not contain terminating character '!'");
111 else {
112 String encodedCellData = rleCellData.substring(0, rleCellData.indexOf(ENCODED_CELL_DATA_TERMINATOR));
113 Matcher matcher = ENCODED_STATUS_RUN_PATTERN.matcher(encodedCellData);
114 List<StatusRun> statusRuns = new ArrayList<>();
115 Coordinate coordinate = new Coordinate(0, 0);
116
117 while (matcher.find()) {
118 StatusRun statusRun = StatusRunHelper.readStatusRun(matcher.group(), coordinate);
119
120 if (Status.LINE_END.equals(statusRun.getStatus()))
121 coordinate = coordinate.withX(0).plusToY(statusRun.getLength());
122 else {
123 coordinate = coordinate.plusToX(statusRun.getLength());
124
125 if (Status.ALIVE.equals(statusRun.getStatus()))
126 statusRuns.add(statusRun);
127 }
128 }
129
130 Set<Coordinate> coordinates = statusRuns.stream()
131 .map(StatusRunHelper::readCoordinates)
132 .reduce(new HashSet<>(), (coordinateAccumulator, coordinateSet) -> {
133 coordinateAccumulator.addAll(coordinateSet);
134 return coordinateAccumulator;
135 });
136
137 return new LiveCells(coordinates);
138 }
139 }
140 }