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 }