diff --git a/.gitignore b/.gitignore
index 5ff6309..44d3a7d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,10 +4,8 @@ target/
!**/src/test/**/target/
### IntelliJ IDEA ###
-.idea/modules.xml
-.idea/jarRepositories.xml
-.idea/compiler.xml
-.idea/libraries/
+.idea
+src/main/resources/application.yaml
*.iws
*.iml
*.ipr
diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index 13566b8..0000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,8 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# Editor-based HTTP Client requests
-/httpRequests/
-# Datasource local storage ignored files
-/dataSources/
-/dataSources.local.xml
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
deleted file mode 100644
index aa00ffa..0000000
--- a/.idea/encodings.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/jpa-buddy.xml b/.idea/jpa-buddy.xml
deleted file mode 100644
index 966d5f5..0000000
--- a/.idea/jpa-buddy.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index 8dc554d..0000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 35eb1dd..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index b4c0aac..2f7ff3a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -26,6 +26,11 @@
5.8.1
test
+
+ org.yaml
+ snakeyaml
+ 2.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..0cf2b88 100644
--- a/src/main/java/org/redis/Main.java
+++ b/src/main/java/org/redis/Main.java
@@ -1,7 +1,58 @@
package org.redis;
+import org.redis.config.RedisConfig;
+import org.redis.service.RedisMap;
+import redis.clients.jedis.JedisPool;
+
public class Main {
public static void main(String[] args) {
- System.out.println("Hello world!");
+ RedisConfig config = new RedisConfig();
+ System.out.println("Connecting to Redis with config: " + config);
+
+ JedisPool jedisPool = config.createJedisPool();
+
+ // Создание экземпляра RedisMap
+ RedisMap redisMap = new RedisMap(jedisPool, "test");
+
+ System.out.println("Redis Map Demo");
+ System.out.println("==============");
+
+ // put операции
+ redisMap.put("name", "John Doe");
+ redisMap.put("email", "john@example.com");
+ redisMap.put("city", "Moscow");
+
+ System.out.println("After put operations:");
+ System.out.println("Size: " + redisMap.size());
+ System.out.println("Get name: " + redisMap.get("name"));
+
+ // containsKey и containsValue
+ System.out.println("\nContains key 'email': " + redisMap.containsKey("email"));
+ System.out.println("Contains value 'Moscow': " + redisMap.containsValue("Moscow"));
+
+ // keySet и values
+ System.out.println("\nKey set: " + redisMap.keySet());
+ System.out.println("Values: " + redisMap.values());
+
+ // remove
+ String removed = redisMap.remove("city");
+ System.out.println("\nRemoved city: " + removed);
+ System.out.println("Size after remove: " + redisMap.size());
+
+ // putAll
+ java.util.Map newEntries = new java.util.HashMap<>();
+ newEntries.put("phone", "123-456-789");
+ newEntries.put("age", "30");
+ redisMap.putAll(newEntries);
+
+ System.out.println("\nAfter putAll:");
+ System.out.println("All entries: " + redisMap.entrySet());
+
+ // clear
+ redisMap.clear();
+ System.out.println("\nAfter clear, is empty: " + redisMap.isEmpty());
+
+ // Закрытие пула соединений
+ jedisPool.close();
}
}
\ No newline at end of file
diff --git a/src/main/java/org/redis/config/RedisConfig.java b/src/main/java/org/redis/config/RedisConfig.java
new file mode 100644
index 0000000..fe77a17
--- /dev/null
+++ b/src/main/java/org/redis/config/RedisConfig.java
@@ -0,0 +1,132 @@
+package org.redis.config;
+
+import org.yaml.snakeyaml.Yaml;
+import redis.clients.jedis.JedisPool;
+import redis.clients.jedis.JedisPoolConfig;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+import java.util.Collections;
+import java.util.Map;
+
+public class RedisConfig {
+
+ private static final Map YAML_REDIS = loadRedisSection();
+
+ private final String host;
+ private final int port;
+ private final String password;
+ private final int timeout;
+
+ public RedisConfig() {
+ this.host = getString("REDIS_HOST", "host", "localhost");
+ this.port = getInt("REDIS_PORT", "port", 6379);
+ this.password = getPassword();
+ this.timeout = getInt("REDIS_TIMEOUT", "timeout", 2000);
+ }
+
+ public boolean hasPassword() {
+ return password != null && !password.isEmpty();
+ }
+
+ public JedisPool createJedisPool() {
+ JedisPoolConfig poolConfig = new JedisPoolConfig();
+ poolConfig.setMaxTotal(10);
+ poolConfig.setMaxIdle(5);
+ poolConfig.setMinIdle(1);
+
+ if (hasPassword()) {
+ return new JedisPool(poolConfig, host, port, timeout, password);
+ }
+
+ return new JedisPool(poolConfig, host, port, timeout);
+ }
+
+ @Override
+ public String toString() {
+ return "RedisConfig{" +
+ "host='" + host + '\'' +
+ ", port=" + port +
+ ", hasPassword=" + hasPassword() +
+ ", timeout=" + timeout +
+ '}';
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Map loadRedisSection() {
+ try (InputStream is = RedisConfig.class.getClassLoader().getResourceAsStream("application.yaml")) {
+ if (is == null) {
+ throw new FileNotFoundException("application.yaml not found");
+ }
+ Yaml yaml = new Yaml();
+ Map root = yaml.load(is);
+
+ if (root == null) {
+ return Collections.emptyMap();
+ }
+
+ Object redis = root.get("redis");
+ if (redis instanceof Map, ?> m) {
+ return (Map) m;
+ }
+
+ return Collections.emptyMap();
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ private static String getString(String envKey, String yamlKey, String defaultValue) {
+ String envValue = System.getenv(envKey);
+ if (envValue != null && !envValue.isEmpty()) {
+ return envValue;
+ }
+
+ Object yamlVal = YAML_REDIS.get(yamlKey);
+ if (yamlVal == null) {
+ return defaultValue;
+ }
+
+ if (yamlVal instanceof String s) {
+ return s.isEmpty() ? defaultValue : s;
+ }
+
+ return String.valueOf(yamlVal);
+ }
+
+ private static int getInt(String envKey, String yamlKey, int defaultValue) {
+ String envValue = System.getenv(envKey);
+ if (envValue != null && !envValue.isEmpty()) {
+ return Integer.parseInt(envValue);
+ }
+
+ Object yamlVal = YAML_REDIS.get(yamlKey);
+ if (yamlVal instanceof Number n) {
+ return n.intValue();
+ }
+
+ if (yamlVal instanceof String s && !s.isEmpty()) {
+ return Integer.parseInt(s);
+ }
+
+ return defaultValue;
+ }
+
+ private static String getPassword() {
+ String envValue = System.getenv("REDIS_PASSWORD");
+ if (envValue != null && !envValue.isEmpty()) {
+ return envValue;
+ }
+
+ Object yamlVal = YAML_REDIS.get("password");
+ if (yamlVal == null) {
+ return null;
+ }
+
+ String s = yamlVal instanceof String ? (String) yamlVal : String.valueOf(yamlVal);
+
+ return s.isEmpty() ? null : s;
+ }
+}
diff --git a/src/main/java/org/redis/service/RedisMap.java b/src/main/java/org/redis/service/RedisMap.java
new file mode 100644
index 0000000..e767e68
--- /dev/null
+++ b/src/main/java/org/redis/service/RedisMap.java
@@ -0,0 +1,152 @@
+package org.redis.service;
+
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.JedisPool;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Прослойка между redis и Map, то есть используем все методы интерфейса Map но данные будут храниться в Redis
+ * {@link #keySet()}, {@link #values()}, {@link #entrySet()} — неизменяемые снимки.
+ */
+public class RedisMap implements Map {
+
+ private static final String STORAGE_PREFIX = "RedisMap:";
+
+ private final JedisPool jedisPool;
+ private final String redisHashKey;
+
+ public RedisMap(JedisPool jedisPool, String namespace) {
+ this.jedisPool = jedisPool;
+ this.redisHashKey = STORAGE_PREFIX + Objects.requireNonNull(namespace, "namespace");
+ }
+
+ static String storageKey(String namespace) {
+ return STORAGE_PREFIX + namespace;
+ }
+
+ private static boolean unsupportedKey(Object key) {
+ return key == null || !(key instanceof String);
+ }
+
+ @Override
+ public int size() {
+ try (Jedis jedis = jedisPool.getResource()) {
+ long len = jedis.hlen(redisHashKey);
+ return len > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) len;
+ }
+ }
+
+ @Override
+ public boolean isEmpty() {
+ try (Jedis jedis = jedisPool.getResource()) {
+ return jedis.hlen(redisHashKey) == 0;
+ }
+ }
+
+ @Override
+ public boolean containsKey(Object key) {
+ if (unsupportedKey(key)) {
+ return false;
+ }
+ try (Jedis jedis = jedisPool.getResource()) {
+ return jedis.hexists(redisHashKey, (String) key);
+ }
+ }
+
+ @Override
+ public boolean containsValue(Object value) {
+ try (Jedis jedis = jedisPool.getResource()) {
+ for (String v : jedis.hvals(redisHashKey)) {
+ if (Objects.equals(v, value)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ @Override
+ public String get(Object key) {
+ if (unsupportedKey(key)) {
+ return null;
+ }
+ try (Jedis jedis = jedisPool.getResource()) {
+ return jedis.hget(redisHashKey, (String) key);
+ }
+ }
+
+ @Override
+ public String put(String key, String value) {
+ Objects.requireNonNull(key, "key");
+ Objects.requireNonNull(value, "value");
+ try (Jedis jedis = jedisPool.getResource()) {
+ String previous = jedis.hget(redisHashKey, key);
+ jedis.hset(redisHashKey, key, value);
+ return previous;
+ }
+ }
+
+ @Override
+ public String remove(Object key) {
+ if (unsupportedKey(key)) {
+ return null;
+ }
+ try (Jedis jedis = jedisPool.getResource()) {
+ String k = (String) key;
+ String previous = jedis.hget(redisHashKey, k);
+ jedis.hdel(redisHashKey, k);
+ return previous;
+ }
+ }
+
+ @Override
+ public void putAll(Map extends String, ? extends String> m) {
+ if (m.isEmpty()) {
+ return;
+ }
+ Map copy = new HashMap<>();
+ for (Entry extends String, ? extends String> e : m.entrySet()) {
+ copy.put(
+ Objects.requireNonNull(e.getKey(), "key"),
+ Objects.requireNonNull(e.getValue(), "value"));
+ }
+ try (Jedis jedis = jedisPool.getResource()) {
+ jedis.hset(redisHashKey, copy);
+ }
+ }
+
+ @Override
+ public void clear() {
+ try (Jedis jedis = jedisPool.getResource()) {
+ jedis.del(redisHashKey);
+ }
+ }
+
+ @Override
+ public Set keySet() {
+ try (Jedis jedis = jedisPool.getResource()) {
+ return Collections.unmodifiableSet(jedis.hkeys(redisHashKey));
+ }
+ }
+
+ @Override
+ public Collection values() {
+ try (Jedis jedis = jedisPool.getResource()) {
+ return Collections.unmodifiableCollection(jedis.hvals(redisHashKey));
+ }
+ }
+
+ @Override
+ public Set> entrySet() {
+ try (Jedis jedis = jedisPool.getResource()) {
+ Map snapshot = new HashMap<>(jedis.hgetAll(redisHashKey));
+ return Collections.unmodifiableMap(snapshot).entrySet();
+ }
+ }
+}
diff --git a/src/main/resources/application.yaml.example b/src/main/resources/application.yaml.example
new file mode 100644
index 0000000..e378d21
--- /dev/null
+++ b/src/main/resources/application.yaml.example
@@ -0,0 +1,5 @@
+redis:
+ host: localhost
+ port: 6379
+ password: null
+ timeout: 2000
diff --git a/src/test/java/org/redis/service/RedisMapTest.java b/src/test/java/org/redis/service/RedisMapTest.java
new file mode 100644
index 0000000..c1f5514
--- /dev/null
+++ b/src/test/java/org/redis/service/RedisMapTest.java
@@ -0,0 +1,235 @@
+package org.redis.service;
+
+import org.junit.jupiter.api.*;
+import org.redis.config.RedisConfig;
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.JedisPool;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+public class RedisMapTest {
+
+ private JedisPool jedisPool;
+ private RedisMap redisMap;
+ private static final String NAMESPACE = "test";
+
+ @BeforeAll
+ void setUp() {
+ RedisConfig config = new RedisConfig();
+ jedisPool = config.createJedisPool();
+ redisMap = new RedisMap(jedisPool, NAMESPACE);
+ }
+
+ @BeforeEach
+ void cleanUp() {
+ try (Jedis jedis = jedisPool.getResource()) {
+ jedis.del(
+ RedisMap.storageKey(NAMESPACE),
+ RedisMap.storageKey("namespace1"),
+ RedisMap.storageKey("namespace2"));
+ }
+ }
+
+ @AfterAll
+ void tearDown() {
+ if (jedisPool != null) {
+ jedisPool.close();
+ }
+ }
+
+ @Test
+ void testPutAndGet() {
+ assertNull(redisMap.put("key1", "value1"));
+ assertEquals("value1", redisMap.get("key1"));
+ assertEquals(1, redisMap.size());
+
+ assertEquals("value1", redisMap.put("key1", "value2"));
+ assertEquals("value2", redisMap.get("key1"));
+ assertEquals(1, redisMap.size());
+ }
+
+ @Test
+ void testGetNonExistentKey() {
+ assertNull(redisMap.get("nonexistent"));
+ }
+
+ @Test
+ void testRemove() {
+ redisMap.put("key1", "value1");
+ redisMap.put("key2", "value2");
+
+ String removed = redisMap.remove("key1");
+ assertEquals("value1", removed);
+ assertNull(redisMap.get("key1"));
+ assertEquals(1, redisMap.size());
+
+ assertNull(redisMap.remove("nonexistent"));
+ }
+
+ @Test
+ void testContainsKey() {
+ redisMap.put("key1", "value1");
+ assertTrue(redisMap.containsKey("key1"));
+ assertFalse(redisMap.containsKey("key2"));
+ }
+
+ @Test
+ void testContainsValue() {
+ redisMap.put("key1", "value1");
+ redisMap.put("key2", "value2");
+
+ assertTrue(redisMap.containsValue("value1"));
+ assertTrue(redisMap.containsValue("value2"));
+ assertFalse(redisMap.containsValue("value3"));
+ }
+
+ @Test
+ void testSize() {
+ assertEquals(0, redisMap.size());
+
+ redisMap.put("key1", "value1");
+ assertEquals(1, redisMap.size());
+
+ redisMap.put("key2", "value2");
+ assertEquals(2, redisMap.size());
+
+ redisMap.remove("key1");
+ assertEquals(1, redisMap.size());
+ }
+
+ @Test
+ void testIsEmpty() {
+ assertTrue(redisMap.isEmpty());
+
+ redisMap.put("key1", "value1");
+ assertFalse(redisMap.isEmpty());
+
+ redisMap.clear();
+ assertTrue(redisMap.isEmpty());
+ }
+
+ @Test
+ void testClear() {
+ redisMap.put("key1", "value1");
+ redisMap.put("key2", "value2");
+ redisMap.put("key3", "value3");
+
+ assertEquals(3, redisMap.size());
+ redisMap.clear();
+ assertEquals(0, redisMap.size());
+ assertNull(redisMap.get("key1"));
+ }
+
+ @Test
+ void testPutAll() {
+ Map entries = new HashMap<>();
+ entries.put("key1", "value1");
+ entries.put("key2", "value2");
+ entries.put("key3", "value3");
+
+ redisMap.putAll(entries);
+
+ assertEquals(3, redisMap.size());
+ assertEquals("value1", redisMap.get("key1"));
+ assertEquals("value2", redisMap.get("key2"));
+ assertEquals("value3", redisMap.get("key3"));
+ }
+
+ @Test
+ void testKeySet() {
+ redisMap.put("key1", "value1");
+ redisMap.put("key2", "value2");
+ redisMap.put("key3", "value3");
+
+ var keySet = redisMap.keySet();
+ assertEquals(3, keySet.size());
+ assertTrue(keySet.contains("key1"));
+ assertTrue(keySet.contains("key2"));
+ assertTrue(keySet.contains("key3"));
+ }
+
+ @Test
+ void testValues() {
+ redisMap.put("key1", "value1");
+ redisMap.put("key2", "value2");
+ redisMap.put("key3", "value3");
+
+ var values = redisMap.values();
+ assertEquals(3, values.size());
+ assertTrue(values.contains("value1"));
+ assertTrue(values.contains("value2"));
+ assertTrue(values.contains("value3"));
+ }
+
+ @Test
+ void testEntrySet() {
+ redisMap.put("key1", "value1");
+ redisMap.put("key2", "value2");
+
+ var entrySet = redisMap.entrySet();
+ assertEquals(2, entrySet.size());
+
+ for (Map.Entry entry : entrySet) {
+ assertTrue(entry.getKey().equals("key1") || entry.getKey().equals("key2"));
+ if (entry.getKey().equals("key1")) {
+ assertEquals("value1", entry.getValue());
+ } else {
+ assertEquals("value2", entry.getValue());
+ }
+ }
+ }
+
+ @Test
+ void testMultipleOperations() {
+ redisMap.put("a", "1");
+ redisMap.put("b", "2");
+
+ assertEquals("1", redisMap.get("a"));
+ assertTrue(redisMap.containsKey("b"));
+ assertFalse(redisMap.containsKey("c"));
+
+ redisMap.put("c", "3");
+ assertEquals(3, redisMap.size());
+
+ redisMap.remove("b");
+ assertEquals(2, redisMap.size());
+ assertFalse(redisMap.containsKey("b"));
+
+ Map additional = new HashMap<>();
+ additional.put("d", "4");
+ additional.put("e", "5");
+ redisMap.putAll(additional);
+
+ assertEquals(4, redisMap.size());
+ assertEquals("4", redisMap.get("d"));
+
+ assertTrue(redisMap.containsValue("1"));
+ assertTrue(redisMap.containsValue("5"));
+
+ redisMap.clear();
+ assertTrue(redisMap.isEmpty());
+ }
+
+ @Test
+ void testNamespaceIsolation() {
+ RedisMap map1 = new RedisMap(jedisPool, "namespace1");
+ RedisMap map2 = new RedisMap(jedisPool, "namespace2");
+
+ map1.put("test", "value1");
+ map2.put("test", "value2");
+
+ assertEquals("value1", map1.get("test"));
+ assertEquals("value2", map2.get("test"));
+
+ assertEquals(1, map1.size());
+ assertEquals(1, map2.size());
+
+ map1.clear();
+ assertNull(map1.get("test"));
+ assertEquals("value2", map2.get("test"));
+ }
+}