From d2599a2904f460d0f3e50fda9b15c338d2700e9c Mon Sep 17 00:00:00 2001 From: u7275858 <110969489+u7275858@users.noreply.github.com> Date: Thu, 10 Nov 2022 06:55:44 +1100 Subject: [PATCH] feature: #1299 Add Identity Map Pattern (#2094) * #1299 IMPLEMENT IDENTITY MAP PATTERN. * #1299 Add docstrings, README, testCases and class diagram. * #1299 Update a comment string in DB implementation. * #1299 Fix code smells. * #1299 Fix code smells and add comments to App.java * #1299 Update constant name. * #1299 Remove java version dependency and update README.md. * #1299 Add lombok to PersonFinder.java. * #1299 Add dependency to maven-assembly-plugin. * #1299 Use java streams in PersonDbSimulatorImplementation.java. * #1299 Add print statements while returning the person object. * #1299 Update README.md. * #1299 Add puml file. * Update README.md --- identity-map/README.md | 201 ++++++++++++++++++ identity-map/etc/IdentityMap.png | Bin 0 -> 83033 bytes identity-map/etc/identity-map.urm.puml | 67 ++++++ identity-map/pom.xml | 69 ++++++ .../java/com/iluwatar/identitymap/App.java | 73 +++++++ .../identitymap/IdNotFoundException.java | 31 +++ .../com/iluwatar/identitymap/IdentityMap.java | 77 +++++++ .../java/com/iluwatar/identitymap/Person.java | 57 +++++ .../identitymap/PersonDbSimulator.java | 36 ++++ .../PersonDbSimulatorImplementation.java | 108 ++++++++++ .../iluwatar/identitymap/PersonFinder.java | 69 ++++++ .../com/iluwatar/identitymap/AppTest.java | 35 +++ .../iluwatar/identitymap/IdentityMapTest.java | 75 +++++++ .../PersonDbSimulatorImplementationTest.java | 122 +++++++++++ .../identitymap/PersonFinderTest.java | 112 ++++++++++ .../com/iluwatar/identitymap/PersonTest.java | 43 ++++ pom.xml | 1 + 17 files changed, 1176 insertions(+) create mode 100644 identity-map/README.md create mode 100644 identity-map/etc/IdentityMap.png create mode 100644 identity-map/etc/identity-map.urm.puml create mode 100644 identity-map/pom.xml create mode 100644 identity-map/src/main/java/com/iluwatar/identitymap/App.java create mode 100644 identity-map/src/main/java/com/iluwatar/identitymap/IdNotFoundException.java create mode 100644 identity-map/src/main/java/com/iluwatar/identitymap/IdentityMap.java create mode 100644 identity-map/src/main/java/com/iluwatar/identitymap/Person.java create mode 100644 identity-map/src/main/java/com/iluwatar/identitymap/PersonDbSimulator.java create mode 100644 identity-map/src/main/java/com/iluwatar/identitymap/PersonDbSimulatorImplementation.java create mode 100644 identity-map/src/main/java/com/iluwatar/identitymap/PersonFinder.java create mode 100644 identity-map/src/test/java/com/iluwatar/identitymap/AppTest.java create mode 100644 identity-map/src/test/java/com/iluwatar/identitymap/IdentityMapTest.java create mode 100644 identity-map/src/test/java/com/iluwatar/identitymap/PersonDbSimulatorImplementationTest.java create mode 100644 identity-map/src/test/java/com/iluwatar/identitymap/PersonFinderTest.java create mode 100644 identity-map/src/test/java/com/iluwatar/identitymap/PersonTest.java diff --git a/identity-map/README.md b/identity-map/README.md new file mode 100644 index 000000000..84ce338cf --- /dev/null +++ b/identity-map/README.md @@ -0,0 +1,201 @@ +--- +title: Identity Map +categories: Data access +language: en +tags: +- Performance +--- + +## Intent + +Ensures that each object gets loaded only once by keeping every loaded object in a map. +Looks up objects using the map when referring to them. + +## Explanation + +Real world example + +> We are writing a program which the user may use to find the records of a given person in a database. + +In plain words + +> Construct an Identity map which stores the records of recently searched for items in the database. When we look +> for the same record next time load it from the map do not go to the database. + +Wikipedia says + +> In the design of DBMS, the identity map pattern is a database access design pattern used to improve performance by providing +a context-specific, in-memory cache to prevent duplicate retrieval of the same object data from the database + +**Programmatic Example** + +* For the purpose of this demonstration assume we have already created a database instance **db**. +* Let's first look at the implementation of a person entity, and it's fields: + +```java +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +@Getter +@Setter +@AllArgsConstructor +public final class Person implements Serializable { + + private static final long serialVersionUID = 1L; + + @EqualsAndHashCode.Include + private int personNationalId; + private String name; + private long phoneNum; + + @Override + public String toString() { + + return "Person ID is : " + personNationalId + " ; Person Name is : " + name + " ; Phone Number is :" + phoneNum; + + } + +} + +``` + +* The following is the implementation of the personFinder which is the entity that the user will utilize in order +to search for a record in our database. It has the relevant DB attached to it. It also maintains an IdentityMap +to store the recently read records. + +```java +@Slf4j +@Getter +@Setter +public class PersonFinder { + private static final long serialVersionUID = 1L; + // Access to the Identity Map + private IdentityMap identityMap = new IdentityMap(); + private PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation(); + /** + * get person corresponding to input ID. + * + * @param key : personNationalId to look for. + */ + public Person getPerson(int key) { + // Try to find person in the identity map + Person person = this.identityMap.getPerson(key); + if (person != null) { + LOGGER.info("Person found in the Map"); + return person; + } else { + // Try to find person in the database + person = this.db.find(key); + if (person != null) { + this.identityMap.addPerson(person); + LOGGER.info("Person found in DB."); + return person; + } + LOGGER.info("Person with this ID does not exist."); + return null; + } + } +} + +``` + +* The identity map field in the above class is simply an abstraction of a hashMap with **personNationalId** +as the keys and the corresponding person object as the value. Here is its implementation: + +```java +@Slf4j +@Getter +public class IdentityMap { + private Map personMap = new HashMap<>(); + /** + * Add person to the map. + */ + public void addPerson(Person person) { + if (!personMap.containsKey(person.getPersonNationalId())) { + personMap.put(person.getPersonNationalId(), person); + } else { // Ensure that addPerson does not update a record. This situation will never arise in our implementation. Added only for testing purposes. + LOGGER.info("Key already in Map"); + } + } + + /** + * Get Person with given id. + * + * @param id : personNationalId as requested by user. + */ + public Person getPerson(int id) { + Person person = personMap.get(id); + if (person == null) { + LOGGER.info("ID not in Map."); + } + return person; + } + + /** + * Get the size of the map. + */ + public int size() { + if (personMap == null) { + return 0; + } + return personMap.size(); + } + +} + +``` + +* Now we should construct a dummy person for demonstration purposes and put that person in our database. + +```java + Person person1 = new Person(1, "John", 27304159); + db.insert(person1); +``` + +* Now let's create a person finder object and look for person with personNationalId = 1(assume that the personFinder +object already has the db and an IdentityMap attached to it.): + +```java + PersonFinder finder = new PersonFinder(); + finder.getPerson(1); +``` + +* At this stage this record will be loaded from the database and the output would be: + +```java + ID not in Map. + Person ID is:1;Person Name is:John;Phone Number is:27304159 + Person found in DB. +``` + +* However, the next we search for this record again we will find it in the map hence we will not need to go +to the database. + +```java + Person ID is:1;Person Name is:John;Phone Number is:27304159 + Person found in Map. +``` + +* If the corresponding record is not in the DB at all then an Exception is thrown. Here is its implementation. + +```java +public class IdNotFoundException extends RuntimeException { + public IdNotFoundException(final String message) { + super(message); + } +} +``` +## Class diagram + +![alt text](./etc/IdentityMap.png "Identity Map Pattern") + +## Applicability + +* The idea behind the Identity Map pattern is that every time we read a record from the database, + we first check the Identity Map to see if the record has already been retrieved. + This allows us to simply return a new reference to the in-memory record rather than creating a new object, + maintaining referential integrity. +* A secondary benefit to the Identity Map is that, since it acts as a cache, + it reduces the number of database calls needed to retrieve objects, which yields a performance enhancement. + +## Credits + +* [Identity Map](https://www.sourcecodeexamples.net/2018/04/identity-map-pattern.html) diff --git a/identity-map/etc/IdentityMap.png b/identity-map/etc/IdentityMap.png new file mode 100644 index 0000000000000000000000000000000000000000..1bac10ebf294d6308cdc301e562a5a6e18e1f970 GIT binary patch literal 83033 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 literal 0 HcmV?d00001 diff --git a/identity-map/etc/identity-map.urm.puml b/identity-map/etc/identity-map.urm.puml new file mode 100644 index 000000000..cdbdbd8f2 --- /dev/null +++ b/identity-map/etc/identity-map.urm.puml @@ -0,0 +1,67 @@ +@startuml +package com.iluwatar.identitymap { + class App { + - LOGGER : Logger {static} + + App() + + main(args : String[]) {static} + } + class IdentityMap { + - LOGGER : Logger {static} + - personMap : Map + + IdentityMap() + + addPerson(person : Person) + + getPerson(id : int) : Person + + getPersonMap() : Map + + size() : int + } + class Person { + - name : String + - personNationalId : int + - phoneNum : long + - serialVersionUID : long {static} + + Person(personNationalId : int, name : String, phoneNum : long) + + equals(o : Object) : boolean + + getName() : String + + getPersonNationalId() : int + + getPhoneNum() : long + + hashCode() : int + + setName(name : String) + + setPersonNationalId(personNationalId : int) + + setPhoneNum(phoneNum : long) + + toString() : String + } + interface PersonDbSimulator { + + delete(int) {abstract} + + find(int) : Person {abstract} + + insert(Person) {abstract} + + update(Person) {abstract} + } + class PersonDbSimulatorImplementation { + ~ ID_STR : String {static} + - LOGGER : Logger {static} + ~ NOT_IN_DATA_BASE : String {static} + - personList : List + + PersonDbSimulatorImplementation() + + delete(id : int) + + find(personNationalID : int) : Person + + insert(person : Person) + + size() : int + + update(person : Person) + } + class PersonFinder { + - LOGGER : Logger {static} + - db : PersonDbSimulatorImplementation + - identityMap : IdentityMap + + PersonFinder() + + getDB() : PersonDbSimulatorImplementation + + getIdentityMap() : IdentityMap + + getPerson(key : int) : Person + + setDb(db : PersonDbSimulatorImplementation) + + setIdentityMap(identityMap : IdentityMap) + } +} +PersonFinder --> "-db" PersonDbSimulatorImplementation +PersonFinder --> "-identityMap" IdentityMap +PersonDbSimulatorImplementation --> "-personList" Person +PersonDbSimulatorImplementation ..|> PersonDbSimulator +@enduml \ No newline at end of file diff --git a/identity-map/pom.xml b/identity-map/pom.xml new file mode 100644 index 000000000..5125e1130 --- /dev/null +++ b/identity-map/pom.xml @@ -0,0 +1,69 @@ + + + + + java-design-patterns + com.iluwatar + 1.26.0-SNAPSHOT + + 4.0.0 + identity-map + + + org.junit.jupiter + junit-jupiter-api + test + + + junit + junit + test + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.identitymap.App + + + + + + + + + diff --git a/identity-map/src/main/java/com/iluwatar/identitymap/App.java b/identity-map/src/main/java/com/iluwatar/identitymap/App.java new file mode 100644 index 000000000..1d0a32256 --- /dev/null +++ b/identity-map/src/main/java/com/iluwatar/identitymap/App.java @@ -0,0 +1,73 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.identitymap; + +import lombok.extern.slf4j.Slf4j; + +/** + * The basic idea behind the Identity Map is to have a series of maps containing objects that have been pulled from the database. + * The below example demonstrates the identity map pattern by creating a sample DB. + * Since only 1 DB has been created we only have 1 map corresponding to it for the purpose of this demo. + * When you load an object from the database, you first check the map. + * If there’s an object in it that corresponds to the one you’re loading, you return it. If not, you go to the database, + * putting the objects on the map for future reference as you load them. + */ +@Slf4j +public class App { + /** + * Program entry point. + * + * @param args command line args. + */ + public static void main(String[] args) { + + // Dummy Persons + Person person1 = new Person(1, "John", 27304159); + Person person2 = new Person(2, "Thomas", 42273631); + Person person3 = new Person(3, "Arthur", 27489171); + Person person4 = new Person(4, "Finn", 20499078); + Person person5 = new Person(5, "Michael", 40599078); + + // Init database + PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation(); + db.insert(person1); + db.insert(person2); + db.insert(person3); + db.insert(person4); + db.insert(person5); + + // Init a personFinder + PersonFinder finder = new PersonFinder(); + finder.setDb(db); + + // Find persons in DataBase not the map. + LOGGER.info(finder.getPerson(2).toString()); + LOGGER.info(finder.getPerson(4).toString()); + LOGGER.info(finder.getPerson(5).toString()); + // Find the person in the map. + LOGGER.info(finder.getPerson(2).toString()); + + } +} diff --git a/identity-map/src/main/java/com/iluwatar/identitymap/IdNotFoundException.java b/identity-map/src/main/java/com/iluwatar/identitymap/IdNotFoundException.java new file mode 100644 index 000000000..8aa71376f --- /dev/null +++ b/identity-map/src/main/java/com/iluwatar/identitymap/IdNotFoundException.java @@ -0,0 +1,31 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.identitymap; + +public class IdNotFoundException extends RuntimeException { + public IdNotFoundException(final String message) { + super(message); + } +} diff --git a/identity-map/src/main/java/com/iluwatar/identitymap/IdentityMap.java b/identity-map/src/main/java/com/iluwatar/identitymap/IdentityMap.java new file mode 100644 index 000000000..e00542f64 --- /dev/null +++ b/identity-map/src/main/java/com/iluwatar/identitymap/IdentityMap.java @@ -0,0 +1,77 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.identitymap; + +import java.util.HashMap; +import java.util.Map; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +/** + * This class stores the map into which we will be caching records after loading them from a DataBase. + * Stores the records as a Hash Map with the personNationalIDs as keys. + */ +@Slf4j +@Getter +public class IdentityMap { + private Map personMap = new HashMap<>(); + /** + * Add person to the map. + */ + public void addPerson(Person person) { + if (!personMap.containsKey(person.getPersonNationalId())) { + personMap.put(person.getPersonNationalId(), person); + } else { // Ensure that addPerson does not update a record. This situation will never arise in our implementation. Added only for testing purposes. + LOGGER.info("Key already in Map"); + } + } + + /** + * Get Person with given id. + * + * @param id : personNationalId as requested by user. + */ + public Person getPerson(int id) { + Person person = personMap.get(id); + if (person == null) { + LOGGER.info("ID not in Map."); + return null; + } + LOGGER.info(person.toString()); + return person; + } + + /** + * Get the size of the map. + */ + public int size() { + if (personMap == null) { + return 0; + } + return personMap.size(); + } + +} diff --git a/identity-map/src/main/java/com/iluwatar/identitymap/Person.java b/identity-map/src/main/java/com/iluwatar/identitymap/Person.java new file mode 100644 index 000000000..0402682f3 --- /dev/null +++ b/identity-map/src/main/java/com/iluwatar/identitymap/Person.java @@ -0,0 +1,57 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.identitymap; + +import java.io.Serializable; + +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * Person definition. + */ +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +@Getter +@Setter +@AllArgsConstructor +public final class Person implements Serializable { + + private static final long serialVersionUID = 1L; + + @EqualsAndHashCode.Include + private int personNationalId; + private String name; + private long phoneNum; + + @Override + public String toString() { + + return "Person ID is : " + personNationalId + " ; Person Name is : " + name + " ; Phone Number is :" + phoneNum; + + } + +} diff --git a/identity-map/src/main/java/com/iluwatar/identitymap/PersonDbSimulator.java b/identity-map/src/main/java/com/iluwatar/identitymap/PersonDbSimulator.java new file mode 100644 index 000000000..9966f11b5 --- /dev/null +++ b/identity-map/src/main/java/com/iluwatar/identitymap/PersonDbSimulator.java @@ -0,0 +1,36 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.identitymap; + +public interface PersonDbSimulator { + Person find(int personNationalID); + + void insert(Person person); + + void update(Person person); + + void delete(int personNationalID); + +} diff --git a/identity-map/src/main/java/com/iluwatar/identitymap/PersonDbSimulatorImplementation.java b/identity-map/src/main/java/com/iluwatar/identitymap/PersonDbSimulatorImplementation.java new file mode 100644 index 000000000..15b59313d --- /dev/null +++ b/identity-map/src/main/java/com/iluwatar/identitymap/PersonDbSimulatorImplementation.java @@ -0,0 +1,108 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.identitymap; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import lombok.extern.slf4j.Slf4j; + +/** + * This is a sample database implementation. The database is in the form of an arraylist which stores records of + * different persons. The personNationalId acts as the primary key for a record. + * Operations : + * -> find (look for object with a particular ID) + * -> insert (insert record for a new person into the database) + * -> update (update the record of a person). To do this, create a new person instance with the same ID as the record you + * want to update. Then call this method with that person as an argument. + * -> delete (delete the record for a particular ID) + */ +@Slf4j +public class PersonDbSimulatorImplementation implements PersonDbSimulator { + + //This simulates a table in the database. To extend logic to multiple tables just add more lists to the implementation. + private List personList = new ArrayList<>(); + static final String NOT_IN_DATA_BASE = " not in DataBase"; + static final String ID_STR = "ID : "; + + @Override + public Person find(int personNationalID) throws IdNotFoundException { + Optional elem = personList.stream().filter(p -> p.getPersonNationalId() == personNationalID).findFirst(); + if (elem.isEmpty()) { + throw new IdNotFoundException(ID_STR + personNationalID + NOT_IN_DATA_BASE); + } + LOGGER.info(elem.get().toString()); + return elem.get(); + } + + @Override + public void insert(Person person) { + Optional elem = personList.stream().filter(p -> p.getPersonNationalId() == person.getPersonNationalId()).findFirst(); + if (elem.isPresent()) { + LOGGER.info("Record already exists."); + return; + } + personList.add(person); + } + + @Override + public void update(Person person) throws IdNotFoundException { + Optional elem = personList.stream().filter(p -> p.getPersonNationalId() == person.getPersonNationalId()).findFirst(); + if (elem.isPresent()) { + elem.get().setName(person.getName()); + elem.get().setPhoneNum(person.getPhoneNum()); + LOGGER.info("Record updated successfully"); + return; + } + throw new IdNotFoundException(ID_STR + person.getPersonNationalId() + NOT_IN_DATA_BASE); + } + + /** + * Delete the record corresponding to given ID from the DB. + * + * @param id : personNationalId for person whose record is to be deleted. + */ + public void delete(int id) throws IdNotFoundException { + Optional elem = personList.stream().filter(p -> p.getPersonNationalId() == id).findFirst(); + if (elem.isPresent()) { + personList.remove(elem.get()); + LOGGER.info("Record deleted successfully."); + return; + } + throw new IdNotFoundException(ID_STR + id + NOT_IN_DATA_BASE); + } + + /** + * Return the size of the database. + */ + public int size() { + if (personList == null) { + return 0; + } + return personList.size(); + } + +} diff --git a/identity-map/src/main/java/com/iluwatar/identitymap/PersonFinder.java b/identity-map/src/main/java/com/iluwatar/identitymap/PersonFinder.java new file mode 100644 index 000000000..d358e24e4 --- /dev/null +++ b/identity-map/src/main/java/com/iluwatar/identitymap/PersonFinder.java @@ -0,0 +1,69 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.identitymap; + +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +/** + * Any object of this class stores a DataBase and an Identity Map. When we try to look for a key we first check if + * it has been cached in the Identity Map and return it if it is indeed in the map. + * If that is not the case then go to the DataBase, get the record, store it in the + * Identity Map and then return the record. Now if we look for the record again we will find it in the table itself which + * will make lookup faster. + */ +@Slf4j +@Getter +@Setter +public class PersonFinder { + private static final long serialVersionUID = 1L; + // Access to the Identity Map + private IdentityMap identityMap = new IdentityMap(); + private PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation(); + /** + * get person corresponding to input ID. + * + * @param key : personNationalId to look for. + */ + public Person getPerson(int key) { + // Try to find person in the identity map + Person person = this.identityMap.getPerson(key); + if (person != null) { + LOGGER.info("Person found in the Map"); + return person; + } else { + // Try to find person in the database + person = this.db.find(key); + if (person != null) { + this.identityMap.addPerson(person); + LOGGER.info("Person found in DB."); + return person; + } + LOGGER.info("Person with this ID does not exist."); + return null; + } + } +} diff --git a/identity-map/src/test/java/com/iluwatar/identitymap/AppTest.java b/identity-map/src/test/java/com/iluwatar/identitymap/AppTest.java new file mode 100644 index 000000000..b946a44a6 --- /dev/null +++ b/identity-map/src/test/java/com/iluwatar/identitymap/AppTest.java @@ -0,0 +1,35 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.identitymap; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + + class AppTest { + @Test + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); + } +} diff --git a/identity-map/src/test/java/com/iluwatar/identitymap/IdentityMapTest.java b/identity-map/src/test/java/com/iluwatar/identitymap/IdentityMapTest.java new file mode 100644 index 000000000..74bbe3d41 --- /dev/null +++ b/identity-map/src/test/java/com/iluwatar/identitymap/IdentityMapTest.java @@ -0,0 +1,75 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.identitymap; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class IdentityMapTest { + @Test + void addToMap(){ + // new instance of an identity map(not connected to any DB here) + IdentityMap idMap = new IdentityMap(); + // Dummy person instances + Person person1 = new Person(11, "Michael", 27304159); + Person person2 = new Person(22, "John", 42273631); + Person person3 = new Person(33, "Arthur", 27489171); + Person person4 = new Person(44, "Finn", 20499078); + // id already in map + Person person5 = new Person(11, "Michael", 40599078); + // All records go into identity map + idMap.addPerson(person1); + idMap.addPerson(person2); + idMap.addPerson(person3); + idMap.addPerson(person4); + idMap.addPerson(person5); + // Test no duplicate in our Map. + Assertions.assertEquals(4,idMap.size(),"Size of the map is incorrect"); + // Test record not updated by add method. + Assertions.assertEquals(27304159,idMap.getPerson(11).getPhoneNum(),"Incorrect return value for phone number"); + } + @Test + void testGetFromMap() { + // new instance of an identity map(not connected to any DB here) + IdentityMap idMap = new IdentityMap(); + // Dummy person instances + Person person1 = new Person(11, "Michael", 27304159); + Person person2 = new Person(22, "John", 42273631); + Person person3 = new Person(33, "Arthur", 27489171); + Person person4 = new Person(44, "Finn", 20499078); + Person person5 = new Person(55, "Michael", 40599078); + // All records go into identity map + idMap.addPerson(person1); + idMap.addPerson(person2); + idMap.addPerson(person3); + idMap.addPerson(person4); + idMap.addPerson(person5); + // Test for dummy persons in the map + Assertions.assertEquals(person1,idMap.getPerson(11),"Incorrect person record returned"); + Assertions.assertEquals(person4,idMap.getPerson(44),"Incorrect person record returned"); + // Test for person with given id not in map + Assertions.assertNull(idMap.getPerson(1), "Incorrect person record returned"); + } +} diff --git a/identity-map/src/test/java/com/iluwatar/identitymap/PersonDbSimulatorImplementationTest.java b/identity-map/src/test/java/com/iluwatar/identitymap/PersonDbSimulatorImplementationTest.java new file mode 100644 index 000000000..6823f3919 --- /dev/null +++ b/identity-map/src/test/java/com/iluwatar/identitymap/PersonDbSimulatorImplementationTest.java @@ -0,0 +1,122 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.identitymap; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class PersonDbSimulatorImplementationTest { + @Test + void testInsert(){ + // DataBase initialization. + PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation(); + Assertions.assertEquals(0,db.size(),"Size of null database should be 0"); + // Dummy persons. + Person person1 = new Person(1, "Thomas", 27304159); + Person person2 = new Person(2, "John", 42273631); + Person person3 = new Person(3, "Arthur", 27489171); + db.insert(person1); + db.insert(person2); + db.insert(person3); + // Test size after insertion. + Assertions.assertEquals(3,db.size(),"Incorrect size for database."); + Person person4 = new Person(4, "Finn", 20499078); + Person person5 = new Person(5, "Michael", 40599078); + db.insert(person4); + db.insert(person5); + // Test size after more insertions. + Assertions.assertEquals(5,db.size(),"Incorrect size for database."); + Person person5duplicate = new Person(5,"Kevin",89589122); + db.insert(person5duplicate); + // Test size after attempt to insert record with duplicate key. + Assertions.assertEquals(5,db.size(),"Incorrect size for data base"); + } + @Test + void findNotInDb(){ + PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation(); + Person person1 = new Person(1, "Thomas", 27304159); + Person person2 = new Person(2, "John", 42273631); + db.insert(person1); + db.insert(person2); + // Test if IdNotFoundException is thrown where expected. + Assertions.assertThrows(IdNotFoundException.class,()->db.find(3)); + } + @Test + void findInDb(){ + PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation(); + Person person1 = new Person(1, "Thomas", 27304159); + Person person2 = new Person(2, "John", 42273631); + db.insert(person1); + db.insert(person2); + Assertions.assertEquals(person2,db.find(2),"Record that was found was incorrect."); + } + @Test + void updateNotInDb(){ + PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation(); + Person person1 = new Person(1, "Thomas", 27304159); + Person person2 = new Person(2, "John", 42273631); + db.insert(person1); + db.insert(person2); + Person person3 = new Person(3,"Micheal",25671234); + // Test if IdNotFoundException is thrown when person with ID 3 is not in DB. + Assertions.assertThrows(IdNotFoundException.class,()->db.update(person3)); + } + @Test + void updateInDb(){ + PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation(); + Person person1 = new Person(1, "Thomas", 27304159); + Person person2 = new Person(2, "John", 42273631); + db.insert(person1); + db.insert(person2); + Person person = new Person(2,"Thomas",42273690); + db.update(person); + Assertions.assertEquals(person,db.find(2),"Incorrect update."); + } + @Test + void deleteNotInDb(){ + PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation(); + Person person1 = new Person(1, "Thomas", 27304159); + Person person2 = new Person(2, "John", 42273631); + db.insert(person1); + db.insert(person2); + // Test if IdNotFoundException is thrown when person with this ID not in DB. + Assertions.assertThrows(IdNotFoundException.class,()->db.delete(3)); + } + @Test + void deleteInDb(){ + PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation(); + Person person1 = new Person(1, "Thomas", 27304159); + Person person2 = new Person(2, "John", 42273631); + db.insert(person1); + db.insert(person2); + // delete the record. + db.delete(1); + // test size of database after deletion. + Assertions.assertEquals(1,db.size(),"Size after deletion is incorrect."); + // try to find deleted record in db. + Assertions.assertThrows(IdNotFoundException.class,()->db.find(1)); + } + +} diff --git a/identity-map/src/test/java/com/iluwatar/identitymap/PersonFinderTest.java b/identity-map/src/test/java/com/iluwatar/identitymap/PersonFinderTest.java new file mode 100644 index 000000000..88f706048 --- /dev/null +++ b/identity-map/src/test/java/com/iluwatar/identitymap/PersonFinderTest.java @@ -0,0 +1,112 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.identitymap; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class PersonFinderTest { + @Test + void personFoundInDB(){ + // personFinderInstance + PersonFinder personFinder = new PersonFinder(); + // init database for our personFinder + PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation(); + // Dummy persons + Person person1 = new Person(1, "John", 27304159); + Person person2 = new Person(2, "Thomas", 42273631); + Person person3 = new Person(3, "Arthur", 27489171); + Person person4 = new Person(4, "Finn", 20499078); + Person person5 = new Person(5, "Michael", 40599078); + // Add data to the database. + db.insert(person1); + db.insert(person2); + db.insert(person3); + db.insert(person4); + db.insert(person5); + personFinder.setDb(db); + + Assertions.assertEquals(person1,personFinder.getPerson(1),"Find person returns incorrect record."); + Assertions.assertEquals(person3,personFinder.getPerson(3),"Find person returns incorrect record."); + Assertions.assertEquals(person2,personFinder.getPerson(2),"Find person returns incorrect record."); + Assertions.assertEquals(person5,personFinder.getPerson(5),"Find person returns incorrect record."); + Assertions.assertEquals(person4,personFinder.getPerson(4),"Find person returns incorrect record."); + } + @Test + void personFoundInIdMap(){ + // personFinderInstance + PersonFinder personFinder = new PersonFinder(); + // init database for our personFinder + PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation(); + // Dummy persons + Person person1 = new Person(1, "John", 27304159); + Person person2 = new Person(2, "Thomas", 42273631); + Person person3 = new Person(3, "Arthur", 27489171); + Person person4 = new Person(4, "Finn", 20499078); + Person person5 = new Person(5, "Michael", 40599078); + // Add data to the database. + db.insert(person1); + db.insert(person2); + db.insert(person3); + db.insert(person4); + db.insert(person5); + personFinder.setDb(db); + // Assure key is not in the ID map. + Assertions.assertFalse(personFinder.getIdentityMap().getPersonMap().containsKey(3)); + // Assure key is in the database. + Assertions.assertEquals(person3,personFinder.getPerson(3),"Finder returns incorrect record."); + // Assure that the record for this key is cached in the Map now. + Assertions.assertTrue(personFinder.getIdentityMap().getPersonMap().containsKey(3)); + // Find the record again. This time it will be found in the map. + Assertions.assertEquals(person3,personFinder.getPerson(3),"Finder returns incorrect record."); + } + @Test + void personNotFoundInDB(){ + PersonFinder personFinder = new PersonFinder(); + // init database for our personFinder + PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation(); + personFinder.setDb(db); + Assertions.assertThrows(IdNotFoundException.class,()->personFinder.getPerson(1)); + // Dummy persons + Person person1 = new Person(1, "John", 27304159); + Person person2 = new Person(2, "Thomas", 42273631); + Person person3 = new Person(3, "Arthur", 27489171); + Person person4 = new Person(4, "Finn", 20499078); + Person person5 = new Person(5, "Michael", 40599078); + db.insert(person1); + db.insert(person2); + db.insert(person3); + db.insert(person4); + db.insert(person5); + personFinder.setDb(db); + // Assure that the database has been updated. + Assertions.assertEquals(person4,personFinder.getPerson(4),"Find returns incorrect record"); + // Assure key is in DB now. + Assertions.assertDoesNotThrow(()->personFinder.getPerson(1)); + // Assure key not in DB. + Assertions.assertThrows(IdNotFoundException.class,()->personFinder.getPerson(6)); + + } +} diff --git a/identity-map/src/test/java/com/iluwatar/identitymap/PersonTest.java b/identity-map/src/test/java/com/iluwatar/identitymap/PersonTest.java new file mode 100644 index 000000000..7c491bf59 --- /dev/null +++ b/identity-map/src/test/java/com/iluwatar/identitymap/PersonTest.java @@ -0,0 +1,43 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.identitymap; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class PersonTest { + @Test + void testEquality(){ + // dummy persons. + Person person1 = new Person(1,"Harry",989950022); + Person person2 = new Person(2,"Kane",989920011); + Assertions.assertNotEquals(person1,person2,"Incorrect equality condition"); + // person with duplicate nationalID. + Person person3 = new Person(2,"John",789012211); + // If nationalID is equal then persons are equal(even if name or phoneNum are different). + // This situation will never arise in this implementation. Only for testing. + Assertions.assertEquals(person2,person3,"Incorrect inequality condition"); + } +} diff --git a/pom.xml b/pom.xml index f821f9434..7ed97bcae 100644 --- a/pom.xml +++ b/pom.xml @@ -215,6 +215,7 @@ composite-view metadata-mapping service-to-worker + identity-map