Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .idea/material_theme_project_new.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 24 additions & 1 deletion src/main/java/org/redis/Main.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,30 @@
package org.redis;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Main {
public static void main(String[] args) {
System.out.println("Hello world!");
Map<String, String> redis = new RedisMap("localhost", 6379);
redis.put("A", "B");
redis.put("C", "C");
redis.put("D", "A");
redis.put("H", "C");
redis.put("Y", "A");


System.out.println(redis.keySet());
System.out.println(redis.values());
System.out.println(redis.entrySet());
System.out.println(redis.size());
redis.keySet().removeAll(List.of("A", "C"));
System.out.println();
System.out.println(redis.keySet());
System.out.println(redis.values());
System.out.println(redis.entrySet());

redis.keySet().retainAll(List.of("H"));
System.out.println(redis);
}
}
348 changes: 348 additions & 0 deletions src/main/java/org/redis/RedisMap.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,348 @@
package org.redis;

import redis.clients.jedis.Jedis;
import java.util.AbstractCollection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
* Реализация интерфейса {@link Map} для строковых типов данных Redis.
* Поддерживает следующие операции Redis: {@code SET}, {@code DEL}
*/
public class RedisMap implements Map<String, String> {

private final Jedis jedis;

/*
Данные поля предназначены для соблюдения контракта интерфейса Map<K,V>, а именно
методов:
Collection<V> values(),
Set<K> keySet(),
Set<Entry<K,V>> entrySet()
*/

private final RedisBackedSet<String> keySet = new RedisKeySet();
private final RedisBackedCollection<String> valuesCollection = new RedisValuesCollection();
private final RedisBackedSet<Entry<String, String>> entrySet = new RedisEntrySet();

public RedisMap(String host, int port) {
this.jedis = new Jedis(Objects.requireNonNull(host), port);
}

public RedisMap(Jedis jedis) {
this.jedis = Objects.requireNonNull(jedis);
}

@Override
public int size() {
return entrySet.size();
}

@Override
public boolean isEmpty() {
return entrySet.isEmpty();
}

@Override
public boolean containsKey(Object key) {
if (!(key instanceof String k)) {
throw new ClassCastException("Key must have String type");
}

return keySet.contains(k);
}

@Override
public boolean containsValue(Object value) {
if (!(value instanceof String v)) {
throw new ClassCastException("Value must have String type");
};

return valuesCollection.contains(v);
}

@Override
public String get(Object key) {
if (!(key instanceof String k)) {
throw new ClassCastException("Key must have String type");
}

return jedis.get(k);
}

@Override
public String put(String key, String value) {
String previous = null;
if (jedis.exists(key)) {
previous = jedis.get(key);
}

String result = jedis.set(Objects.requireNonNull(key), Objects.requireNonNull(value));
if (result.equals("OK")) {
if (previous != null) {
keySet.remove(previous);
}

Entry<String, String> entry = Map.entry(key, value);
keySet.internalAdd(entry.getKey());
valuesCollection.internalAdd(entry.getValue());
entrySet.internalAdd(entry);
return previous;
} else {
throw new SavingKeyValueErrorException(
String.format("Unexpected exception while saving key-value pair: key: %s, value: %s, result: %s", key, value, result)
);
}
}

@Override
public String remove(Object key) {
if (!(key instanceof String k)) {
throw new ClassCastException("Key must have String type");
}

if (!jedis.exists(k)) {
return null;
}

String result = jedis.getDel(k);
keySet.remove(k);
return result;
}

@Override
public void putAll(Map<? extends String, ? extends String> map) {
Objects.requireNonNull(map).forEach(this::put);
}

@Override
public void clear() {
jedis.keys("*").forEach(jedis::del);
entrySet.unwrapped().clear();
keySet.unwrapped().clear();
valuesCollection.unwrapped().clear();
}

@Override
public Set<String> keySet() {
return this.keySet;
}

@Override
public Collection<String> values() {
return this.valuesCollection;
}

@Override
public Set<Entry<String, String>> entrySet() {
return this.entrySet;
}

@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("{ ");
entrySet.forEach(entry -> stringBuilder.append('\"').append(entry.getKey()).append('\"')
.append("=").append('\"').append(entry.getValue()).append("\" ")
);
return stringBuilder.append("}").toString();
}

/**
* Итератор предназначенный для итерирования по коллекциям реализующим {@link RedisBackedCollection}
* @param <T> тип элементов коллекции
*/
private abstract static class RedisIterator<T> implements Iterator<T> {

protected final Iterator<T> iterator;
protected T currentItem;

public RedisIterator(Iterator<T> iterator) {
this.iterator = iterator;
}

@Override
public boolean hasNext() {
return iterator.hasNext();
}

@Override
public T next() {
currentItem = iterator.next();
return currentItem;
}

@Override
public void remove() {
if (currentItem == null) {
throw new IllegalStateException("next() method does not called yet");
}
removeBacked();
}

/**
* Метод предназначенный для синхронизации удаления между коллекциями
* {@link RedisMap#keySet}, {@link RedisMap#entrySet}, {@link RedisMap#valuesCollection}
*/
protected abstract void removeBacked();
}

/**
* Коллекция предназначенная для реализации контракта методов
* {@link Map#keySet()}, {@link Map#entrySet()}, {@link Map#values()}
* @param <T> тип элементов коллекции
*/
private abstract static class RedisBackedCollection<T> extends AbstractCollection<T> {

protected final Collection<T> collection;

public RedisBackedCollection(Collection<T> collection) {
this.collection = collection;
}

/**
* Добавляет элементы в коллекцию, данный метод предназначен для поддержания контракта методов
* {@link Map#keySet()}, {@link Map#entrySet()}, {@link Map#values()}
* @param value значение для добавления
*/
protected void internalAdd(T value) {
collection.add(value);
}

/**
* Возвращает внутреннюю коллекцию {@link RedisBackedCollection}
* @return внутренняя коллекция {@link RedisBackedCollection}
*/
protected Collection<T> unwrapped() {
return this.collection;
}

@Override
public boolean add(T t) {
throw new UnsupportedOperationException("Unsupported operation for backed collection");
}

@Override
public boolean addAll(Collection<? extends T> c) {
throw new UnsupportedOperationException("Unsupported operation for backed collection");
}

@Override
public RedisIterator<T> iterator() {
return redisIterator();
}

@Override
public int size() {
return collection.size();
}

/**
* Возвращает итератора реализующий {@link RedisIterator}
* @return итератор реализующий {@link RedisIterator}
*/
protected abstract RedisIterator<T> redisIterator();
}

/**
* Расширение класса {@link RedisBackedCollection} для реализации интерфейса {@link Set}
* @param <T> тип элементов коллекции
*/
private abstract static class RedisBackedSet<T> extends RedisBackedCollection<T> implements Set<T> {
public RedisBackedSet(Set<T> set) {
super(set);
}
}

/**
* Конкретная реализация {@link RedisBackedCollection} для реализации контракта метода интерфейса {@link Map#values()}
*/
private final class RedisValuesCollection extends RedisBackedCollection<String> {

public RedisValuesCollection() {
super(new ArrayList<>());
}

@Override
protected RedisIterator<String> redisIterator() {
return new RedisIterator<>(super.collection.iterator()) {
@Override
protected void removeBacked() {
Entry<String, String> entryToRemove = entrySet.stream()
.filter(entry -> entry.getValue().equals(this.currentItem))
.findAny()
.orElse(null);

if (entryToRemove == null) {
return;
}

jedis.del(entryToRemove.getKey());
this.iterator.remove();
keySet.remove(entryToRemove.getKey());
entrySet.unwrapped().remove(entryToRemove);
}
};
}
}

/**
* Конкретная реализация {@link RedisBackedCollection} для реализации контракта метода интерфейса {@link Map#keySet()}
*/
private final class RedisKeySet extends RedisBackedSet<String> {

public RedisKeySet() {
super(new HashSet<>());
}

@Override
protected RedisIterator<String> redisIterator() {
return new RedisIterator<>(super.collection.iterator()) {
@Override
protected void removeBacked() {
Entry<String, String> entryToRemove = entrySet.stream()
.filter(entry -> entry.getKey().equals(this.currentItem))
.findAny()
.orElse(null);

if (entryToRemove == null) {
return;
}

jedis.del(entryToRemove.getKey());
this.iterator.remove();
valuesCollection.remove(entryToRemove.getValue());
entrySet.unwrapped().remove(entryToRemove);
}
};
}
}

/**
* Конкретная реализация {@link RedisBackedCollection} для реализации контракта метода интерфейса {@link Map#entrySet()
*/
private final class RedisEntrySet extends RedisBackedSet<Entry<String, String>> {

public RedisEntrySet() {
super(new HashSet<>());
}

@Override
protected RedisIterator<Entry<String, String>> redisIterator() {
return new RedisIterator<>(super.collection.iterator()) {
@Override
protected void removeBacked() {
jedis.del(this.currentItem.getKey());
this.iterator.remove();
keySet.unwrapped().remove(this.currentItem.getKey());
valuesCollection.unwrapped().remove(this.currentItem.getValue());
}
};
}
}
}
Loading