diff --git a/.gitignore b/.gitignore index 9d37e1d..fb49a67 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /.idea/ /target/ /tags +/src/main/resources/output.csv diff --git a/README.md b/README.md new file mode 100644 index 0000000..1f47fcd --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# Volunteers + +## Participants : + +- [Nicolas Laroche](https://github.com/Laroche-Nicolas/) +- [Guillaume Chateauroux](https://github.com/chxGuillaume/) +- [Julien Seixas](https://github.com/MrZyr0/) +- [Geoffrey Parrier](https://github.com/geoffreyparrier/) + +![](https://britishcivilwars.ncl.ac.uk/assets/game-runway/images/confetti.gif) diff --git a/src/main/java/App.java b/src/main/java/App.java index 0ac03f0..3da79df 100644 --- a/src/main/java/App.java +++ b/src/main/java/App.java @@ -1,5 +1,6 @@ import org.example.volunteers.Cleaner; import org.example.volunteers.Volunteer; +import org.example.volunteers.utils.FieldsSanitizer; import java.io.FileWriter; import java.io.IOException; @@ -25,7 +26,7 @@ public static void main(String[] args) throws IOException { .map(tokens -> new Volunteer(tokens.get(0), tokens.get(1), tokens.get(2), tokens.get(3), tokens.get(4))) .collect(toList()); - List outputVolunteers = Cleaner.cleanUp(inputVolunteers); + List outputVolunteers = Cleaner.cleanUp(inputVolunteers, new FieldsSanitizer()); PrintWriter writer = new PrintWriter(new FileWriter("src/main/resources/output.csv")); outputVolunteers.forEach(writer::println); diff --git a/src/main/java/org/example/volunteers/Cleaner.java b/src/main/java/org/example/volunteers/Cleaner.java index f4df12b..0628c2c 100644 --- a/src/main/java/org/example/volunteers/Cleaner.java +++ b/src/main/java/org/example/volunteers/Cleaner.java @@ -1,12 +1,70 @@ package org.example.volunteers; +import org.example.volunteers.utils.FieldsSanitizer; + import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.regex.Pattern; public class Cleaner { - public static List cleanUp(List volunteers) { + public static List cleanUp(List volunteers, FieldsSanitizer fieldsSanitizer) { + HashMap volunteersFullName = new HashMap<>(); + HashMap volunteersNickName = new HashMap<>(); + + List result = new ArrayList<>(); + // This function should contain your dark magic. // For now, it simply returns a copy of the initial list. - return new ArrayList<>(volunteers); + for (Volunteer volunteer : volunteers) { + volunteer.firstName = fieldsSanitizer.clearName(volunteer.firstName); + volunteer.lastName = fieldsSanitizer.clearName(volunteer.lastName); + volunteer.eMail = fieldsSanitizer.clearEmail(volunteer.eMail); + volunteer.phone = fieldsSanitizer.clearPhone(volunteer.phone); + } + + for (Volunteer volunteer : volunteers) { + String fullName = volunteer.firstName.toLowerCase() + volunteer.lastName.toLowerCase(); + + if (fullName.equals("")) { + Volunteer originalVolunteer = volunteersNickName.get(volunteer.nickName.toLowerCase()); + + if (originalVolunteer == null) { + volunteersNickName.put(volunteer.nickName.toLowerCase(), volunteer); + result.add(volunteer); + } else { + processDuplicate(originalVolunteer, volunteer); + } + } else { + Volunteer originalVolunteer = volunteersFullName.get(fullName); + + if (originalVolunteer == null) { + volunteersFullName.put(fullName, volunteer); + result.add(volunteer); + } else { + processDuplicate(originalVolunteer, volunteer); + } + } + } + + return result; + } + + private static void processDuplicate(Volunteer original, Volunteer duplicate) { + if (!original.eMail.contains(duplicate.eMail)) { + original.eMail += getSeparator(original.eMail) + duplicate.eMail; + } + + if (!original.phone.contains(duplicate.phone)) { + original.phone += getSeparator(original.phone) + duplicate.phone; + } + + if (!original.nickName.contains(duplicate.nickName)) { + original.nickName += getSeparator(original.nickName) + duplicate.nickName; + } + } + + private static String getSeparator(String str) { + return (!str.equals("") ? ";" : ""); } } diff --git a/src/main/java/org/example/volunteers/Volunteer.java b/src/main/java/org/example/volunteers/Volunteer.java index 491d00f..3ce88d2 100644 --- a/src/main/java/org/example/volunteers/Volunteer.java +++ b/src/main/java/org/example/volunteers/Volunteer.java @@ -5,18 +5,18 @@ import static java.util.stream.Collectors.joining; public final class Volunteer { - public final String firstName; - public final String lastName; - public final String nickName; - public final String eMail; - public final String phone; + public String firstName; + public String lastName; + public String nickName; + public String eMail; + public String phone; public Volunteer( - String firstName, - String lastName, - String nickName, - String eMail, - String phone + String firstName, + String lastName, + String nickName, + String eMail, + String phone ) { this.firstName = firstName; this.lastName = lastName; @@ -27,8 +27,8 @@ public Volunteer( @Override public String toString() { - return Arrays.stream(new String[]{firstName,lastName,nickName,eMail,phone}) - .map(attribute -> String.format("\"%s\"", attribute)) - .collect(joining(";")); + return Arrays.stream(new String[]{firstName, lastName, nickName, eMail, phone}) + .map(attribute -> String.format("\"%s\"", attribute)) + .collect(joining(";")); } } diff --git a/src/main/java/org/example/volunteers/utils/FieldsSanitizer.java b/src/main/java/org/example/volunteers/utils/FieldsSanitizer.java new file mode 100644 index 0000000..5731e97 --- /dev/null +++ b/src/main/java/org/example/volunteers/utils/FieldsSanitizer.java @@ -0,0 +1,38 @@ +package org.example.volunteers.utils; + +import java.text.Normalizer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class FieldsSanitizer { + + public String clearName(String name) { + return name.replaceAll("[^\\p{L} ,.'-]", ""); + } + + public String clearPhone(String phone) { + String result = phone.replaceAll("[^\\d.]|\\.", ""); + + result = result.substring(Math.max(result.length() - 9, 0)); + + return result.length() == 9 ? "0" + result : ""; + } + + public String clearEmail(String email) { + if (email == null || email.isEmpty()) { + return ""; + } + + String normalizedEmail = Normalizer.normalize(email.toLowerCase(), Normalizer.Form.NFD); + String result = normalizedEmail.replaceAll("\\p{M}", ""); + + String mailRegex = "(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)])"; + + Matcher mailMatcher = Pattern.compile(mailRegex).matcher(result); + if (mailMatcher.find()) { + return mailMatcher.group(); + } + + return ""; + } +} diff --git a/src/test/java/org/example/volunteers/FieldsSanitizerTesting.java b/src/test/java/org/example/volunteers/FieldsSanitizerTesting.java new file mode 100644 index 0000000..4c10646 --- /dev/null +++ b/src/test/java/org/example/volunteers/FieldsSanitizerTesting.java @@ -0,0 +1,25 @@ +package org.example.volunteers; + +import org.example.volunteers.utils.FieldsSanitizer; + +public class FieldsSanitizerTesting extends FieldsSanitizer { + public Integer clearNameCount = 0; + public Integer clearPhoneCount = 0; + public Integer clearEmailCount = 0; + + public String clearName(String name) { + clearNameCount += 1; + return super.clearName(name); + } + + public String clearPhone(String phone) { + clearPhoneCount += 1; + return super.clearPhone(phone); + } + + public String clearEmail(String email) { + clearEmailCount += 1; + return super.clearEmail(email); + } + +} diff --git a/src/test/java/org/example/volunteers/TestCleaner.java b/src/test/java/org/example/volunteers/TestCleaner.java new file mode 100644 index 0000000..4b3dd6a --- /dev/null +++ b/src/test/java/org/example/volunteers/TestCleaner.java @@ -0,0 +1,134 @@ +package org.example.volunteers; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class TestCleaner { + + // Integration test + @Test + public void shouldCallEachFieldSanitizerForEachVolunteers() { + volunteers.add(new Volunteer("leo", "duff", "duff", "leo.duff@voleur.ytb", "+330069696969")); + FieldsSanitizerTesting fieldsSanitizer = new FieldsSanitizerTesting(); + + Cleaner.cleanUp(volunteers, fieldsSanitizer); + + Assertions.assertEquals(2, fieldsSanitizer.clearNameCount); + Assertions.assertEquals(1, fieldsSanitizer.clearPhoneCount); + Assertions.assertEquals(1, fieldsSanitizer.clearEmailCount); + } + + // Unit tests + @Test + public void shouldRegroupMails() { + addVolunteers( + new Volunteer("leo", "duff", "duff", "leo.duff@voleur.ytb", "+330069696969"), + new Volunteer("LEO", "duff", "duff", "leo.duff@voleur.twitch", "+330069696969"), + new Volunteer("leo", "DUFF", "duff", "leo.duff@voleur.dailmotion", "+330069696969") + ); + + List expectedVolunteers = new ArrayList<>(); + expectedVolunteers.add(new Volunteer("leo", "duff", "duff", "leo.duff@voleur.ytb;leo.duff@voleur.twitch;leo.duff@voleur.dailmotion", "0069696969")); + + whenCleaningVolunteers(); + thenExpectedListIs(expectedVolunteers); + } + + @Test + public void shouldRegroupNickNames() { + addVolunteers( + new Volunteer("leo", "duff", "duff", "leo.duff@voleur.ytb", "+330069696969"), + new Volunteer("LEO", "duff", "miguel", "leo.duff@voleur.ytb", "+330069696969"), + new Volunteer("leo", "DUFF", "judo", "leo.duff@voleur.ytb", "+330069696969") + ); + + List expectedVolunteers = new ArrayList<>(); + expectedVolunteers.add(new Volunteer("leo", "duff", "duff;miguel;judo", "leo.duff@voleur.ytb", "0069696969")); + + whenCleaningVolunteers(); + thenExpectedListIs(expectedVolunteers); + } + + @Test + public void shouldRegroupPhones() { + addVolunteers( + new Volunteer("leo", "duff", "duff", "leo.duff@voleur.ytb", "+330069696969"), + new Volunteer("LEO", "duff", "duff", "leo.duff@voleur.ytb", "+330069696969"), + new Volunteer("leo", "DUFF", "duff", "leo.duff@voleur.ytb", "+330069696968") + ); + + List expectedVolunteers = new ArrayList<>(); + expectedVolunteers.add(new Volunteer("leo", "duff", "duff", "leo.duff@voleur.ytb", "0069696969;0069696968")); + + whenCleaningVolunteers(); + thenExpectedListIs(expectedVolunteers); + } + + @Test + public void shouldRegroupEmptyNickNames() { + addVolunteers( + new Volunteer("leo", "duff", "", "leo.duff@voleur.ytb", "+330069696969"), + new Volunteer("LEO", "duff", "miguel", "leo.duff@voleur.ytb", "+330069696969"), + new Volunteer("leo", "DUFF", "judo", "leo.duff@voleur.ytb", "+330069696969") + ); + + List expectedVolunteers = new ArrayList<>(); + expectedVolunteers.add(new Volunteer("leo", "duff", "miguel;judo", "leo.duff@voleur.ytb", "0069696969")); + + whenCleaningVolunteers(); + thenExpectedListIs(expectedVolunteers); + } + + @Test + public void shouldRegroupPhonesOnNickName() { + addVolunteers( + new Volunteer("", "", "roche", "example@voleur.ytb", "+330069696967"), + new Volunteer("", "", "miguel", "example@voleur.ytb", "+330069696969"), + new Volunteer("", "", "roche", "example@voleur.ytb", "+330069696968") + ); + + List expectedVolunteers = new ArrayList<>(); + expectedVolunteers.add(new Volunteer("", "", "roche", "example@voleur.ytb", "0069696967;0069696968")); + expectedVolunteers.add(new Volunteer("", "", "miguel", "example@voleur.ytb", "0069696969")); + + whenCleaningVolunteers(); + thenExpectedListIs(expectedVolunteers); + } + + @Test + public void shouldRegroupEmailsOnNickName() { + addVolunteers( + new Volunteer("", "", "roche", "example@voleur.ytb", "+330069696968"), + new Volunteer("", "", "miguel", "example@voleur.ytb", "+330069696969"), + new Volunteer("", "", "roche", "example@voleur.twitch", "+330069696968") + ); + + List expectedVolunteers = new ArrayList<>(); + expectedVolunteers.add(new Volunteer("", "", "roche", "example@voleur.ytb;example@voleur.twitch", "0069696968")); + expectedVolunteers.add(new Volunteer("", "", "miguel", "example@voleur.ytb", "0069696969")); + + whenCleaningVolunteers(); + thenExpectedListIs(expectedVolunteers); + } + + private void addVolunteers(Volunteer... volunteers) { + Collections.addAll(this.volunteers, volunteers); + } + + private void whenCleaningVolunteers() { + FieldsSanitizerTesting fieldsSanitizer = new FieldsSanitizerTesting(); + cleared = Cleaner.cleanUp(volunteers, fieldsSanitizer); + } + + private void thenExpectedListIs(List expected) { + Assertions.assertEquals(expected.toString(), cleared.toString()); + } + + private final List volunteers = new ArrayList<>(); + private List cleared = new ArrayList<>(); + +} diff --git a/src/test/java/org/example/volunteers/TestFieldsSanitizerEmail.java b/src/test/java/org/example/volunteers/TestFieldsSanitizerEmail.java new file mode 100644 index 0000000..faf97ad --- /dev/null +++ b/src/test/java/org/example/volunteers/TestFieldsSanitizerEmail.java @@ -0,0 +1,41 @@ +package org.example.volunteers; + +import org.example.volunteers.utils.FieldsSanitizer; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class TestFieldsSanitizerEmail { + FieldsSanitizer fieldsSanitizer = new FieldsSanitizer(); + + + @Test + public void shouldRemoveAccents() { + Assertions.assertEquals("leo.dupuis@quiches.lardon", fieldsSanitizer.clearEmail("léo.dupuis@quiches.lardon")); + } + + @Test + public void shouldLowerEmail() { + Assertions.assertEquals("leo.dupuis@quiches.lardon", fieldsSanitizer.clearEmail("Leo.Dupuis@quiches.lardon")); + } + + @Test + public void shouldRemoveLastDotWhenExist() { + Assertions.assertEquals("leo.dupuis@quiches.lardon", fieldsSanitizer.clearEmail("Leo.Dupuis@quiches.lardon.")); + } + + @Test + public void shouldReturnEmptyStringIfInvalid() { + Assertions.assertEquals("", fieldsSanitizer.clearEmail("leo.dupuis@quiches")); + Assertions.assertEquals("", fieldsSanitizer.clearEmail("leo.dupuis")); + } + + @Test + public void shouldReturnEmptyStringForEmptyField() { + Assertions.assertEquals("", fieldsSanitizer.clearEmail("")); + } + + @Test + public void shouldReturnEmailWhenFieldIsValid() { + Assertions.assertEquals("leo.dupuis@quiches.ovh", fieldsSanitizer.clearEmail("leo.dupuis@quiches.ovh")); + } +} diff --git a/src/test/java/org/example/volunteers/TestFieldsSanitizerName.java b/src/test/java/org/example/volunteers/TestFieldsSanitizerName.java new file mode 100644 index 0000000..7f50e2e --- /dev/null +++ b/src/test/java/org/example/volunteers/TestFieldsSanitizerName.java @@ -0,0 +1,33 @@ +package org.example.volunteers; + +import org.example.volunteers.utils.FieldsSanitizer; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class TestFieldsSanitizerName { + FieldsSanitizer fieldsSanitizer = new FieldsSanitizer(); + + @Test + public void shouldKeepLatinSpecialChar() { + Assertions.assertEquals("Léo", fieldsSanitizer.clearName("Léo")); + Assertions.assertEquals("Raphaël", fieldsSanitizer.clearName("Raphaël")); + Assertions.assertEquals("Владимир", fieldsSanitizer.clearName("Владимир")); + } + + @Test + public void digitsShouldBeRemoved() { + Assertions.assertEquals("Mathieu", fieldsSanitizer.clearName("Mathieu69")); + } + + @Test + public void specialCharsShouldBeRemoved() { + Assertions.assertEquals("Math", fieldsSanitizer.clearName("Math@")); + Assertions.assertEquals("", fieldsSanitizer.clearName("[][;$]")); + } + + @Test + public void emptyNameShouldStayEmpty() { + Assertions.assertEquals("", fieldsSanitizer.clearName("")); + } + +} diff --git a/src/test/java/org/example/volunteers/TestFieldsSanitizerPhone.java b/src/test/java/org/example/volunteers/TestFieldsSanitizerPhone.java new file mode 100644 index 0000000..c2e2631 --- /dev/null +++ b/src/test/java/org/example/volunteers/TestFieldsSanitizerPhone.java @@ -0,0 +1,44 @@ +package org.example.volunteers; + +import org.example.volunteers.utils.FieldsSanitizer; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class TestFieldsSanitizerPhone { + FieldsSanitizer fieldsSanitizer = new FieldsSanitizer(); + + @Test + public void spacesShouldBeRemoved() { + Assertions.assertEquals("0069696969", fieldsSanitizer.clearPhone("00 69 69 69 69")); + } + + @Test + public void shouldBeTenDigits() { + Assertions.assertEquals("", fieldsSanitizer.clearPhone("00 69 69 69")); + Assertions.assertEquals("0069696969", fieldsSanitizer.clearPhone("0069696969")); + } + + @Test + public void indicatorShouldBeRemoved() { + Assertions.assertEquals("0069696969", fieldsSanitizer.clearPhone("+33069696969")); + Assertions.assertEquals("0069696969", fieldsSanitizer.clearPhone("+330069696969")); + Assertions.assertEquals("0069696969", fieldsSanitizer.clearPhone("+33(0)069696969")); + } + + @Test + public void specialCharsShouldBeRemoved() { + Assertions.assertEquals("0069696969", fieldsSanitizer.clearPhone("00-69-69-69-69")); + Assertions.assertEquals("0069696969", fieldsSanitizer.clearPhone("(00)69-69-69-69")); + Assertions.assertEquals("0069696969", fieldsSanitizer.clearPhone("(00)69.69.69.69")); + Assertions.assertEquals("0069696969", fieldsSanitizer.clearPhone("00.69.69.69.69")); + Assertions.assertEquals("0069696969", fieldsSanitizer.clearPhone("00,69,69,69,69")); + } + + @Test + public void shouldReturnOnlyDigits() { + Assertions.assertEquals("0069696969", fieldsSanitizer.clearPhone("006r969y6969")); + Assertions.assertTrue(fieldsSanitizer.clearPhone("0069696969").matches("\\d+"), "Should be Digits Only"); + Assertions.assertTrue(fieldsSanitizer.clearPhone("0069696969").matches("\\d+"), "Should be Digits Only"); + } + +}