Skip to content

Instantly share code, notes, and snippets.

@Pyeroh
Created January 2, 2019 04:01
Show Gist options
  • Save Pyeroh/374b8768926a6f06175421311530ed24 to your computer and use it in GitHub Desktop.
Save Pyeroh/374b8768926a6f06175421311530ed24 to your computer and use it in GitHub Desktop.
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