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 }