diff --git a/pom.xml b/pom.xml
index b4c0aac..ccb5dc2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -26,6 +26,32 @@
5.8.1
test
+
+ org.projectlombok
+ lombok
+ 1.18.38
+ provided
+
+
+ ch.qos.logback
+ logback-classic
+ 1.5.20
+
+
+ org.slf4j
+ slf4j-api
+ 2.0.13
+
+
+ net.datafaker
+ datafaker
+ 2.5.3
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.17.2
+
\ No newline at end of file
diff --git a/src/main/java/org/redis/Main.java b/src/main/java/org/redis/Main.java
index 68b3db6..72fa992 100644
--- a/src/main/java/org/redis/Main.java
+++ b/src/main/java/org/redis/Main.java
@@ -1,7 +1,50 @@
package org.redis;
+import lombok.extern.slf4j.Slf4j;
+import org.redis.model.Student;
+import redis.clients.jedis.JedisPool;
+import redis.clients.jedis.JedisPoolConfig;
+
+import java.util.List;
+
+@Slf4j
public class Main {
+ private static final String HOST = "localhost";
+ private static final int PORT = 6379;
+ private static final String REDIS_KEY = "students";
+
+
public static void main(String[] args) {
- System.out.println("Hello world!");
+
+ RedisMap map = getStringStudentRedisMap();
+
+ log.info("user1: {}", map.get("user1"));
+ log.info("user2: {}", map.get("user2"));
+
+ log.info("Map size: {}", map.size());
+ log.info("Keys: {}", map.keySet());
+ log.info("Values: {}", map.values());
+
+ map.forEach((key, value) -> log.info("Entry: key={}, value={}", key, value));
+ }
+
+ private static RedisMap getStringStudentRedisMap() {
+ JedisPool pool = new JedisPool(new JedisPoolConfig(), HOST, PORT);
+
+ RedisMap map = new RedisMap<>(
+ pool,
+ REDIS_KEY,
+ String.class,
+ Student.class
+ );
+
+ map.clear();
+
+ Student s1 = new Student("Sergey", 20, List.of("Java", "Spring", "Redis"));
+ Student s2 = new Student("Andrew", 22, List.of("Python", "Django"));
+
+ map.put("user1", s1);
+ map.put("user2", s2);
+ return map;
}
}
\ No newline at end of file
diff --git a/src/main/java/org/redis/RedisMap.java b/src/main/java/org/redis/RedisMap.java
new file mode 100644
index 0000000..e5989ca
--- /dev/null
+++ b/src/main/java/org/redis/RedisMap.java
@@ -0,0 +1,190 @@
+package org.redis;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.extern.slf4j.Slf4j;
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.JedisPool;
+import redis.clients.jedis.params.ScanParams;
+import redis.clients.jedis.resps.ScanResult;
+
+import java.util.AbstractMap;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@Slf4j
+public class RedisMap implements Map {
+ private static final String KEY_IS_NULL_STRING = "Key is null";
+
+ private final JedisPool pool;
+ private final String redisKey;
+ private final ObjectMapper mapper;
+ private final Class keyClass;
+ private final Class valueClass;
+
+ public RedisMap(JedisPool pool, String redisKey, Class keyClass, Class valueClass) {
+ this.pool = pool;
+ this.redisKey = redisKey;
+ this.keyClass = keyClass;
+ this.valueClass = valueClass;
+ this.mapper = new ObjectMapper();
+ }
+
+ private String serialize(Object value) {
+ try {
+ return mapper.writeValueAsString(value);
+ } catch (JsonProcessingException e) {
+ log.error(e.getMessage());
+ throw new IllegalStateException("JSON serialization failed: " + value, e);
+ }
+ }
+
+ private T deserialize(String json, Class type) {
+ try {
+ return mapper.readValue(json, type);
+ } catch (Exception e) {
+ log.error(e.getMessage());
+ throw new IllegalStateException("JSON deserialization failed: " + json, e);
+ }
+ }
+
+ @Override
+ public int size() {
+ try (Jedis jedis = pool.getResource()) {
+ return Math.toIntExact(jedis.hlen(redisKey));
+ }
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return size() == 0;
+ }
+
+ @Override
+ public boolean containsKey(Object key) {
+ Objects.requireNonNull(key, KEY_IS_NULL_STRING);
+
+ try (Jedis jedis = pool.getResource()) {
+ return jedis.hexists(redisKey, serialize(key));
+ }
+ }
+
+ @Override
+ public boolean containsValue(Object value) {
+ if (!valueClass.isInstance(value)) {
+ return false;
+ }
+
+ try (Jedis jedis = pool.getResource()) {
+ String cursor = ScanParams.SCAN_POINTER_START;
+ ScanParams params = new ScanParams().count(100);
+
+ do {
+ ScanResult> scanResult = jedis.hscan(redisKey, cursor, params);
+
+ for (Map.Entry entry : scanResult.getResult()) {
+ V val = deserialize(entry.getValue(), valueClass);
+ if (val.equals(value)) {
+ return true;
+ }
+ }
+
+ cursor = scanResult.getCursor();
+ } while (!cursor.equals(ScanParams.SCAN_POINTER_START));
+ }
+
+ return false;
+ }
+
+ @Override
+ public V get(Object key) {
+ Objects.requireNonNull(key, KEY_IS_NULL_STRING);
+
+ try (Jedis jedis = pool.getResource()) {
+ String json = jedis.hget(redisKey, serialize(key));
+ return json != null ? deserialize(json, valueClass) : null;
+ }
+ }
+
+ @Override
+ public V put(K key, V value) {
+ Objects.requireNonNull(key, KEY_IS_NULL_STRING);
+ Objects.requireNonNull(value, "Value is null");
+
+ V old = get(key);
+ try (Jedis jedis = pool.getResource()) {
+ jedis.hset(redisKey, serialize(key), serialize(value));
+ }
+ return old;
+ }
+
+ @Override
+ public V remove(Object key) {
+ Objects.requireNonNull(key, KEY_IS_NULL_STRING);
+
+ V old = get(key);
+ try (Jedis jedis = pool.getResource()) {
+ jedis.hdel(redisKey, serialize(key));
+ }
+ return old;
+ }
+
+ @Override
+ public void putAll(Map extends K, ? extends V> m) {
+ Objects.requireNonNull(m, "Map is null");
+
+ Map map = m.entrySet()
+ .stream()
+ .collect(Collectors.toMap(
+ e -> serialize(e.getKey()),
+ e -> serialize(e.getValue())
+ ));
+
+ try (Jedis jedis = pool.getResource()) {
+ jedis.hset(redisKey, map);
+ }
+ }
+
+ @Override
+ public void clear() {
+ try (Jedis jedis = pool.getResource()) {
+ jedis.del(redisKey);
+ }
+ }
+
+ @Override
+ public Set keySet() {
+ try (Jedis jedis = pool.getResource()) {
+ return jedis.hkeys(redisKey)
+ .stream()
+ .map(k -> deserialize(k, keyClass))
+ .collect(Collectors.toSet());
+ }
+ }
+
+ @Override
+ public Collection values() {
+ try (Jedis jedis = pool.getResource()) {
+ return jedis.hvals(redisKey)
+ .stream()
+ .map(v -> deserialize(v, valueClass))
+ .toList();
+ }
+ }
+
+ @Override
+ public Set> entrySet() {
+ try (Jedis jedis = pool.getResource()) {
+ return jedis.hgetAll(redisKey).entrySet()
+ .stream()
+ .map(e -> new AbstractMap.SimpleEntry<>(
+ deserialize(e.getKey(), keyClass),
+ deserialize(e.getValue(), valueClass)))
+ .collect(Collectors.toSet());
+ }
+ }
+}
+
diff --git a/src/main/java/org/redis/model/Student.java b/src/main/java/org/redis/model/Student.java
new file mode 100644
index 0000000..b5b721f
--- /dev/null
+++ b/src/main/java/org/redis/model/Student.java
@@ -0,0 +1,31 @@
+package org.redis.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+import java.util.Objects;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class Student {
+ private String name;
+ private int age;
+ private List skills;
+
+ @Override
+ public boolean equals(Object object) {
+ if (object == null || getClass() != object.getClass()) return false;
+ Student student = (Student) object;
+ return age == student.age
+ && Objects.equals(name, student.name)
+ && Objects.equals(skills, student.skills);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, age, skills);
+ }
+}
diff --git a/src/test/java/org/redis/RedisMapTest.java b/src/test/java/org/redis/RedisMapTest.java
new file mode 100644
index 0000000..e4ab6eb
--- /dev/null
+++ b/src/test/java/org/redis/RedisMapTest.java
@@ -0,0 +1,153 @@
+package org.redis;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.redis.model.Student;
+import redis.clients.jedis.JedisPool;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class RedisMapTest {
+
+ private static JedisPool jedisPool;
+ private RedisMap map;
+
+ @BeforeAll
+ static void beforeAll() {
+ jedisPool = new JedisPool("localhost", 6379);
+ }
+
+ @BeforeEach
+ void setUp() {
+ map = new RedisMap<>(
+ jedisPool,
+ "test:students",
+ String.class,
+ Student.class
+ );
+
+ map.clear();
+ }
+
+ @Test
+ void testPutAndGet() {
+ Student s = new Student("A", 22, List.of("Java", "Redis"));
+ map.put("user1", s);
+
+ Student actual = map.get("user1");
+
+ assertNotNull(actual);
+ assertEquals("A", actual.getName());
+ }
+
+ @Test
+ void testSize() {
+ map.put("u1", new Student("A", 15, List.of()));
+ map.put("u2", new Student("B", 25, List.of()));
+
+ assertEquals(2, map.size());
+ }
+
+ @Test
+ void testContainsKey() {
+ map.put("exists", new Student("A", 1, List.of()));
+
+ assertTrue(map.containsKey("exists"));
+ assertFalse(map.containsKey("none"));
+ }
+
+ @Test
+ void testContainsValue() {
+ Student s1 = new Student("A", 10, List.of("A"));
+ Student s2 = new Student("B", 20, List.of("B"));
+
+ map.put("1", s1);
+ map.put("2", s2);
+
+ assertTrue(map.containsValue(s1));
+ assertFalse(map.containsValue(new Student("A", 999, List.of())));
+ }
+
+ @Test
+ void testRemove() {
+ Student s = new Student("toDelete", 999, List.of());
+ map.put("del", s);
+
+ Student removed = map.remove("del");
+
+ assertEquals("toDelete", removed.getName());
+ assertNull(map.get("del"));
+ }
+
+ @Test
+ void testKeySet() {
+ map.put("a", new Student("A", 10, List.of()));
+ map.put("b", new Student("B", 20, List.of()));
+
+ Set keys = map.keySet();
+
+ assertEquals(Set.of("a", "b"), keys);
+ }
+
+ @Test
+ void testValues() {
+ Student s1 = new Student("A", 10, List.of("A"));
+ Student s2 = new Student("B", 20, List.of("B"));
+
+ map.put("x1", s1);
+ map.put("x2", s2);
+
+ Collection values = map.values();
+
+ assertTrue(values.contains(s1));
+ assertTrue(values.contains(s2));
+ assertEquals(2, values.size());
+ }
+
+ @Test
+ void testEntrySet() {
+ Student s1 = new Student("A", 10, List.of("A"));
+ Student s2 = new Student("B", 20, List.of("B"));
+
+ map.put("k1", s1);
+ map.put("k2", s2);
+
+ Set> entries = map.entrySet();
+
+ assertEquals(2, entries.size());
+ assertTrue(entries.stream().anyMatch(e -> e.getKey().equals("k1")));
+ assertTrue(entries.stream().anyMatch(e -> e.getKey().equals("k2")));
+ }
+
+ @Test
+ void testPutAll() {
+ Map source = new HashMap<>();
+ source.put("s1", new Student("A", 1, List.of()));
+ source.put("s2", new Student("B", 2, List.of()));
+
+ map.putAll(source);
+
+ assertEquals(2, map.size());
+ }
+
+ @Test
+ void testClear() {
+ map.put("1", new Student("A", 1, List.of()));
+ map.put("2", new Student("B", 2, List.of()));
+
+ map.clear();
+
+ assertTrue(map.isEmpty());
+ }
+}
\ No newline at end of file