Created
January 2, 2019 04:01
-
-
Save Pyeroh/374b8768926a6f06175421311530ed24 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.time.LocalDate; | |
import java.time.Month; | |
import java.time.format.DateTimeFormatter; | |
import java.util.ArrayList; | |
import java.util.Arrays; | |
import java.util.Collections; | |
import java.util.Comparator; | |
import java.util.HashMap; | |
import java.util.HashSet; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Objects; | |
import java.util.Optional; | |
import java.util.Set; | |
import java.util.StringJoiner; | |
import java.util.stream.Collectors; | |
import java.util.stream.IntStream; | |
import java.util.stream.Stream; | |
public class Inspector { | |
private static final LocalDate EXPIRATION_DATE = LocalDate.of(1982, Month.NOVEMBER, 22); | |
private static final DateTimeFormatter DATE_PATTERN = DateTimeFormatter.ofPattern("yyyy.MM.dd"); | |
private final Set<Country> allowedCountries = new HashSet<>(); | |
private final Set<Country> deniedCountries = new HashSet<>(); | |
private final Map<DocumentType, Set<Country>> requiredDocuments = new HashMap<>(); | |
private final Map<String, Set<Country>> requiredVaccinations = new HashMap<>(); | |
private Optional<String> wantedCriminal; | |
private boolean workersRequireWorkPass; | |
public void receiveBulletin(String bulletin) { | |
debugParameter(bulletin, "/"); | |
wantedCriminal = Optional.empty(); | |
for (String bulletinPart : bulletin.split("\n")) { | |
if (bulletinPart.startsWith("Allow citizens of")) { | |
List<Country> newlyAllowCountries = Arrays.stream(bulletinPart | |
.replace("Allow citizens of", "") | |
.trim() | |
.split(", ?")) | |
.map(Country::byName) | |
.collect(Collectors.toList()); | |
allowedCountries.addAll(newlyAllowCountries); | |
deniedCountries.removeAll(newlyAllowCountries); | |
} else if (bulletinPart.startsWith("Deny citizens of")) { | |
List<Country> newlyDeniedCountries = Arrays.stream(bulletinPart | |
.replace("Deny citizens of", "") | |
.trim() | |
.split(", ?")) | |
.map(Country::byName) | |
.collect(Collectors.toList()); | |
allowedCountries.removeAll(newlyDeniedCountries); | |
deniedCountries.addAll(newlyDeniedCountries); | |
} else if (bulletinPart.startsWith("Wanted by the State")) { | |
wantedCriminal = Optional.of(bulletinPart.replace("Wanted by the State:", "").trim()); | |
} else if (bulletinPart.contains("require")) { | |
String[] require = bulletinPart.split(" ?require ?"); | |
String who = require[0]; | |
String what = require[1]; | |
if (who.contains("Worker")) { | |
workersRequireWorkPass = !who.contains("no longer"); | |
continue; | |
} | |
Set<Country> countries = findCountries(who); | |
DocumentType documentType = findDocument(what); | |
if (documentType == DocumentType.CERTIFICATE_OF_VACCINATION) { | |
String vaccination = what.replace("vaccination", "").trim(); | |
Set<Country> countriesForVaccination = requiredVaccinations.computeIfAbsent(vaccination, v -> new HashSet<>()); | |
if (who.contains("no longer")) { | |
countriesForVaccination.removeAll(countries); | |
} else { | |
countriesForVaccination.addAll(countries); | |
} | |
} | |
Set<Country> countriesForDocument = requiredDocuments.computeIfAbsent(documentType, d -> new HashSet<>()); | |
if (who.contains("no longer")) { | |
countriesForDocument.removeAll(countries); | |
} else { | |
countriesForDocument.addAll(countries); | |
} | |
} | |
} | |
} | |
private Set<Country> findCountries(String descriptor) { | |
if (descriptor.contains("Entrant")) { | |
return new HashSet<>(Arrays.asList(Country.values())); | |
} | |
if (descriptor.contains("Foreigner")) { | |
return Arrays.stream(Country.values()) | |
.filter(c -> c != Country.ARSTOTZKA) | |
.collect(Collectors.toSet()); | |
} | |
if (descriptor.contains("Citizen")) { | |
return Arrays.stream(descriptor.replace("Citizens of", "") | |
.replace("no longer", "") | |
.trim() | |
.split(", ?")) | |
.map(Country::byName) | |
.collect(Collectors.toSet()); | |
} | |
throw new RuntimeException("Unsupported \"who\" descriptor"); | |
} | |
private DocumentType findDocument(String descriptor) { | |
if (descriptor.contains("vaccination")) { | |
return DocumentType.CERTIFICATE_OF_VACCINATION; | |
} | |
return DocumentType.byKey(descriptor.trim() | |
.replaceAll(" +", " ") | |
.replace(' ', '_')); | |
} | |
public String inspect(Map<String, String> person) { | |
debugParameter(person, "#"); | |
List<List<Information>> personInformations = person.entrySet().stream() | |
.map(this::getInformations) | |
.collect(Collectors.toList()); | |
debugState(personInformations); | |
// Criminal | |
if (wantedCriminal.isPresent() | |
&& getInformationStream(personInformations, Name.class) | |
.map(p -> String.format("%s %s", p.firstName, p.lastName)) | |
.anyMatch(wantedCriminal.get()::equals)) { | |
return "Detainment: Entrant is a wanted criminal."; | |
} | |
// Mismatching information | |
Optional<String> mismatchingInformation = getMismatchingInformation(personInformations); | |
if (mismatchingInformation.isPresent()) { | |
return String.format("Detainment: %s mismatch.", mismatchingInformation.get()); | |
} | |
// Preparing data for further computing | |
Set<DocumentType> documentTypes = getInformationStream(personInformations, Document.class) | |
.map(d -> d.documentType) | |
.collect(Collectors.toSet()); | |
Country country = getInformationStream(personInformations, Nation.class) | |
.distinct() | |
.map(n -> n.nation) | |
.findFirst() | |
.orElseGet(() -> documentTypes.contains(DocumentType.ID_CARD) ? Country.ARSTOTZKA : null); | |
// Required documents | |
List<DocumentType> requiredDocumentForCountry = requiredDocuments.entrySet().stream() | |
.filter(e -> e.getValue().contains(country)) | |
.map(Map.Entry::getKey) | |
.collect(Collectors.toList()); | |
requiredDocumentForCountry.removeAll(documentTypes); | |
if (documentTypes.contains(DocumentType.DIPLOMATIC_AUTHORIZATION) || documentTypes.contains(DocumentType.GRANT_OF_ASYLUM)) { | |
requiredDocumentForCountry.remove(DocumentType.ACCESS_PERMIT); | |
} | |
if (documentTypes.contains(DocumentType.ACCESS_PERMIT) | |
&& getInformationStream(personInformations, WithPurpose.class).findFirst().get().purpose.equals("WORK") | |
&& workersRequireWorkPass | |
&& getInformationStream(personInformations, WorkPass.class).count() == 0) { | |
requiredDocumentForCountry.add(DocumentType.WORK_PASS); | |
} | |
if (!requiredDocumentForCountry.isEmpty()) { | |
return "Entry denied: missing required " + requiredDocumentForCountry.stream().min(Comparator.comparingInt(d -> d.priority)).get().key.replace('_', ' ') + "."; | |
} | |
// Banned nation | |
if (!allowedCountries.contains(country) || deniedCountries.contains(country)) { | |
return "Entry denied: citizen of banned nation."; | |
} | |
// Check diplomatic authorization | |
if (documentTypes.contains(DocumentType.DIPLOMATIC_AUTHORIZATION) | |
&& !getInformationStream(personInformations, Access.class).findFirst().get().access.contains(Country.ARSTOTZKA)) { | |
return "Entry denied: invalid diplomatic authorization."; | |
} | |
// Expiration | |
Optional<DocumentType> firstExpiredDocument = getInformationStream(personInformations, Document.class) | |
.filter(d -> d.dateOfExpiration.isEqual(EXPIRATION_DATE) || d.dateOfExpiration.isBefore(EXPIRATION_DATE)) | |
.map(d -> d.documentType) | |
.findFirst(); | |
if (firstExpiredDocument.isPresent()) { | |
return "Entry denied: " + firstExpiredDocument.get().key.replace('_', ' ') + " expired."; | |
} | |
// Required vaccination | |
Set<String> vaccines = getInformationStream(personInformations, Vaccine.class) | |
.map(v -> v.vaccines) | |
.flatMap(Set::stream) | |
.collect(Collectors.toSet()); | |
List<String> requiredVaccinationsForCountry = requiredVaccinations.entrySet().stream() | |
.filter(e -> e.getValue().contains(country)) | |
.map(Map.Entry::getKey) | |
.collect(Collectors.toList()); | |
requiredVaccinationsForCountry.removeAll(vaccines); | |
if (!requiredVaccinationsForCountry.isEmpty()) { | |
return "Entry denied: missing required vaccination."; | |
} | |
// Passing message | |
if (country == Country.ARSTOTZKA) { | |
return "Glory to " + Country.ARSTOTZKA.name + "."; | |
} else { | |
return "Cause no trouble."; | |
} | |
} | |
private Optional<String> getMismatchingInformation(List<List<Information>> personInformations) { | |
if (getInformationStream(personInformations, Identifier.class).distinct().count() > 1) { | |
return Optional.of("ID number"); | |
} | |
if (getInformationStream(personInformations, Name.class).distinct().count() > 1) { | |
return Optional.of("name"); | |
} | |
if (getInformationStream(personInformations, Nation.class).distinct().count() > 1) { | |
return Optional.of("nationality"); | |
} | |
return Optional.empty(); | |
} | |
private <I extends Information> Stream<I> getInformationStream(List<List<Information>> personInformations, Class<I> infoClass) { | |
return personInformations.stream() | |
.flatMap(List::stream) | |
.filter(infoClass::isInstance) | |
.map(infoClass::cast); | |
} | |
private List<Information> getInformations(Map.Entry<String, String> document) { | |
List<Information> informations = new ArrayList<>(); | |
Map<String, String> infosFromDocument = Arrays.stream(document.getValue().split("\n")) | |
.map(s -> s.split(": ?")) | |
.collect(Collectors.toMap(s -> s[0], s -> s[1])); | |
informations.add(new Document(DocumentType.byKey(document.getKey()), LocalDate.parse(infosFromDocument.getOrDefault("EXP", EXPIRATION_DATE.plusDays(1).format(DATE_PATTERN)), DATE_PATTERN))); | |
if (infosFromDocument.containsKey("ID#")) { | |
informations.add(new Identifier(infosFromDocument.get("ID#"))); | |
} | |
if (infosFromDocument.containsKey("NAME")) { | |
String[] name = infosFromDocument.get("NAME").split(", "); | |
informations.add(new Name(name[1], | |
name[0])); | |
} | |
if (infosFromDocument.containsKey("SEX")) { | |
informations.add(new Sex(infosFromDocument.get("SEX"))); | |
} | |
if (infosFromDocument.containsKey("DOB")) { | |
informations.add(new DateOfBirth(LocalDate.parse(infosFromDocument.get("DOB"), DATE_PATTERN))); | |
} | |
if (infosFromDocument.containsKey("HEIGHT")) { | |
informations.add(new Size(infosFromDocument.get("HEIGHT"), | |
infosFromDocument.get("WEIGHT"))); | |
} | |
if (infosFromDocument.containsKey("NATION")) { | |
informations.add(new Nation(Country.byName(infosFromDocument.get("NATION")))); | |
} | |
if (infosFromDocument.containsKey("ISS")) { | |
informations.add(new IssuingCity(infosFromDocument.get("ISS"))); | |
} | |
if (infosFromDocument.containsKey("PURPOSE")) { | |
informations.add(new WithPurpose(infosFromDocument.get("PURPOSE"), | |
infosFromDocument.get("DURATION"))); | |
} | |
if (infosFromDocument.containsKey("ACCESS")) { | |
informations.add(new Access(Arrays.stream(infosFromDocument.get("ACCESS").split(", ")) | |
.map(Country::byName) | |
.collect(Collectors.toSet()))); | |
} | |
if (infosFromDocument.containsKey("FIELD")) { | |
informations.add(new WorkPass(infosFromDocument.get("FIELD"))); | |
} | |
if (infosFromDocument.containsKey("VACCINES")) { | |
informations.add(new Vaccine(Arrays.stream(infosFromDocument.get("VACCINES").split(", ")).collect(Collectors.toSet()))); | |
} | |
return informations; | |
} | |
private void debugParameter(Object parameter, String s) { | |
String parameterSeparator = IntStream.range(0, 10).mapToObj(i -> s).collect(Collectors.joining()); | |
System.out.println(parameterSeparator); | |
System.out.println(parameter); | |
System.out.println(parameterSeparator); | |
} | |
private void debugState(List<List<Information>> personInformations) { | |
String separator = IntStream.range(0, 10).mapToObj(i -> "=").collect(Collectors.joining()); | |
System.out.println(separator); | |
System.out.println("Allowed : " + allowedCountries); | |
System.out.println("Denied : " + deniedCountries); | |
System.out.println("Documents : " + requiredDocuments); | |
System.out.println("Vaccinations : " + requiredVaccinations); | |
System.out.println(wantedCriminal); | |
System.out.println("Work pass required : " + workersRequireWorkPass); | |
System.out.println(personInformations); | |
System.out.println(separator); | |
System.out.println(); | |
} | |
enum Country { | |
ARSTOTZKA("Arstotzka"), | |
ANTEGRIA("Antegria"), | |
IMPOR("Impor"), | |
KOLECHIA("Kolechia"), | |
OBRISTAN("Obristan"), | |
REPUBLIA("Republia"), | |
UNITED_FEDERATION("United Federation"); | |
final String name; | |
Country(String name) { | |
this.name = name; | |
} | |
public static Country byName(String name) { | |
return Arrays.stream(values()) | |
.filter(c -> c.name.equals(name)) | |
.findFirst() | |
.orElseThrow(() -> new RuntimeException("No country for name " + name)); | |
} | |
} | |
enum DocumentType { | |
PASSPORT("passport", 0), | |
ID_CARD("ID_card", 1), | |
ACCESS_PERMIT("access_permit", 2), | |
WORK_PASS("work_pass", 3), | |
GRANT_OF_ASYLUM("grant_of_asylum", 4), | |
CERTIFICATE_OF_VACCINATION("certificate_of_vaccination", 5), | |
DIPLOMATIC_AUTHORIZATION("diplomatic_authorization", 6); | |
final String key; | |
final int priority; | |
DocumentType(String key, int priority) { | |
this.key = key; | |
this.priority = priority; | |
} | |
public static DocumentType byKey(String key) { | |
return Arrays.stream(values()) | |
.filter(d -> d.key.equals(key)) | |
.findFirst() | |
.orElseThrow(() -> new RuntimeException("No document type for key " + key)); | |
} | |
} | |
interface Information { | |
} | |
public static class Document implements Information { | |
final DocumentType documentType; | |
final LocalDate dateOfExpiration; | |
Document(DocumentType documentType, LocalDate dateOfExpiration) { | |
this.documentType = documentType; | |
this.dateOfExpiration = dateOfExpiration; | |
} | |
@Override | |
public boolean equals(Object o) { | |
if (this == o) return true; | |
if (o == null || getClass() != o.getClass()) return false; | |
Document document = (Document) o; | |
return dateOfExpiration.equals(document.dateOfExpiration); | |
} | |
@Override | |
public int hashCode() { | |
return Objects.hash(dateOfExpiration); | |
} | |
@Override | |
public String toString() { | |
return new StringJoiner(", ", Document.class.getSimpleName() + "[", "]") | |
.add("documentType=" + documentType) | |
.add("dateOfExpiration=" + dateOfExpiration) | |
.toString(); | |
} | |
} | |
public static class Identifier implements Information { | |
final String id; | |
public Identifier(String id) { | |
this.id = id; | |
} | |
@Override | |
public boolean equals(Object o) { | |
if (this == o) return true; | |
if (o == null || getClass() != o.getClass()) return false; | |
Identifier identifier1 = (Identifier) o; | |
return id.equals(identifier1.id); | |
} | |
@Override | |
public int hashCode() { | |
return Objects.hash(id); | |
} | |
@Override | |
public String toString() { | |
return new StringJoiner(", ", Identifier.class.getSimpleName() + "[", "]") | |
.add("id='" + id + "'") | |
.toString(); | |
} | |
} | |
public static class Name implements Information { | |
final String firstName; | |
final String lastName; | |
Name(String firstName, String lastName) { | |
this.firstName = firstName; | |
this.lastName = lastName; | |
} | |
@Override | |
public boolean equals(Object o) { | |
if (this == o) return true; | |
if (o == null || getClass() != o.getClass()) return false; | |
Name name = (Name) o; | |
return firstName.equals(name.firstName) && | |
lastName.equals(name.lastName); | |
} | |
@Override | |
public int hashCode() { | |
return Objects.hash(firstName, lastName); | |
} | |
@Override | |
public String toString() { | |
return new StringJoiner(", ", Name.class.getSimpleName() + "[", "]") | |
.add("firstName='" + firstName + "'") | |
.add("lastName='" + lastName + "'") | |
.toString(); | |
} | |
} | |
public static class Nation implements Information { | |
final Country nation; | |
Nation(Country nation) { | |
this.nation = nation; | |
} | |
@Override | |
public boolean equals(Object o) { | |
if (this == o) return true; | |
if (o == null || getClass() != o.getClass()) return false; | |
Nation nation1 = (Nation) o; | |
return nation == nation1.nation; | |
} | |
@Override | |
public int hashCode() { | |
return Objects.hash(nation); | |
} | |
@Override | |
public String toString() { | |
return new StringJoiner(", ", Nation.class.getSimpleName() + "[", "]") | |
.add("nation=" + nation) | |
.toString(); | |
} | |
} | |
public static class IssuingCity implements Information { | |
final String issuingCity; | |
IssuingCity(String issuingCity) { | |
this.issuingCity = issuingCity; | |
} | |
@Override | |
public boolean equals(Object o) { | |
if (this == o) return true; | |
if (o == null || getClass() != o.getClass()) return false; | |
IssuingCity that = (IssuingCity) o; | |
return issuingCity.equals(that.issuingCity); | |
} | |
@Override | |
public int hashCode() { | |
return Objects.hash(issuingCity); | |
} | |
@Override | |
public String toString() { | |
return new StringJoiner(", ", IssuingCity.class.getSimpleName() + "[", "]") | |
.add("issuingCity='" + issuingCity + "'") | |
.toString(); | |
} | |
} | |
public static class WorkPass implements Information { | |
final String field; | |
public WorkPass(String field) { | |
this.field = field; | |
} | |
@Override | |
public boolean equals(Object o) { | |
if (this == o) return true; | |
if (o == null || getClass() != o.getClass()) return false; | |
WorkPass workPass = (WorkPass) o; | |
return Objects.equals(field, workPass.field); | |
} | |
@Override | |
public int hashCode() { | |
return Objects.hash(field); | |
} | |
@Override | |
public String toString() { | |
return new StringJoiner(", ", WorkPass.class.getSimpleName() + "[", "]") | |
.add("field='" + field + "'") | |
.toString(); | |
} | |
} | |
public static class Sex implements Information { | |
final String sex; | |
Sex(String sex) { | |
this.sex = sex; | |
} | |
@Override | |
public boolean equals(Object o) { | |
if (this == o) return true; | |
if (o == null || getClass() != o.getClass()) return false; | |
Sex sex1 = (Sex) o; | |
return sex.equals(sex1.sex); | |
} | |
@Override | |
public int hashCode() { | |
return Objects.hash(sex); | |
} | |
@Override | |
public String toString() { | |
return new StringJoiner(", ", Sex.class.getSimpleName() + "[", "]") | |
.add("sex='" + sex + "'") | |
.toString(); | |
} | |
} | |
public static class DateOfBirth implements Information { | |
final LocalDate dateOfBirth; | |
DateOfBirth(LocalDate dateOfBirth) { | |
this.dateOfBirth = dateOfBirth; | |
} | |
@Override | |
public boolean equals(Object o) { | |
if (this == o) return true; | |
if (o == null || getClass() != o.getClass()) return false; | |
DateOfBirth that = (DateOfBirth) o; | |
return dateOfBirth.equals(that.dateOfBirth); | |
} | |
@Override | |
public int hashCode() { | |
return Objects.hash(dateOfBirth); | |
} | |
@Override | |
public String toString() { | |
return new StringJoiner(", ", DateOfBirth.class.getSimpleName() + "[", "]") | |
.add("dateOfBirth=" + dateOfBirth) | |
.toString(); | |
} | |
} | |
public static class Size implements Information { | |
final String height; | |
final String weight; | |
Size(String height, String weight) { | |
this.height = height; | |
this.weight = weight; | |
} | |
@Override | |
public boolean equals(Object o) { | |
if (this == o) return true; | |
if (o == null || getClass() != o.getClass()) return false; | |
Size size = (Size) o; | |
return height.equals(size.height) && | |
weight.equals(size.weight); | |
} | |
@Override | |
public int hashCode() { | |
return Objects.hash(height, weight); | |
} | |
@Override | |
public String toString() { | |
return new StringJoiner(", ", Size.class.getSimpleName() + "[", "]") | |
.add("height='" + height + "'") | |
.add("weight='" + weight + "'") | |
.toString(); | |
} | |
} | |
public static class WithPurpose implements Information { | |
final String purpose; | |
final String duration; | |
WithPurpose(String purpose, String duration) { | |
this.purpose = purpose; | |
this.duration = duration; | |
} | |
@Override | |
public boolean equals(Object o) { | |
if (this == o) return true; | |
if (o == null || getClass() != o.getClass()) return false; | |
WithPurpose that = (WithPurpose) o; | |
return purpose.equals(that.purpose) && | |
duration.equals(that.duration); | |
} | |
@Override | |
public int hashCode() { | |
return Objects.hash(purpose, duration); | |
} | |
@Override | |
public String toString() { | |
return new StringJoiner(", ", WithPurpose.class.getSimpleName() + "[", "]") | |
.add("purpose='" + purpose + "'") | |
.add("duration='" + duration + "'") | |
.toString(); | |
} | |
} | |
public static class Access implements Information { | |
final Set<Country> access; | |
public Access(Set<Country> access) { | |
this.access = Collections.unmodifiableSet(access); | |
} | |
@Override | |
public boolean equals(Object o) { | |
if (this == o) return true; | |
if (o == null || getClass() != o.getClass()) return false; | |
Access access1 = (Access) o; | |
return access.equals(access1.access); | |
} | |
@Override | |
public int hashCode() { | |
return Objects.hash(access); | |
} | |
@Override | |
public String toString() { | |
return new StringJoiner(", ", Access.class.getSimpleName() + "[", "]") | |
.add("access=" + access) | |
.toString(); | |
} | |
} | |
public static class Vaccine implements Information { | |
final Set<String> vaccines; | |
public Vaccine(Set<String> vaccines) { | |
this.vaccines = Collections.unmodifiableSet(vaccines); | |
} | |
@Override | |
public boolean equals(Object o) { | |
if (this == o) return true; | |
if (o == null || getClass() != o.getClass()) return false; | |
Vaccine vaccine = (Vaccine) o; | |
return vaccines.equals(vaccine.vaccines); | |
} | |
@Override | |
public int hashCode() { | |
return Objects.hash(vaccines); | |
} | |
@Override | |
public String toString() { | |
return new StringJoiner(", ", Vaccine.class.getSimpleName() + "[", "]") | |
.add("vaccines=" + vaccines) | |
.toString(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment