Skip to content

Commit

Permalink
Patch for Issue #12 first delivery includes Map enhancements to now s…
Browse files Browse the repository at this point in the history
…upport all combinations of dataTypes in CollectionMaps:

K = Primitive, V = Primitive; K = Primitive, V = Model; K = Model, V = Primitive; K = Model, V = Model
  • Loading branch information
gsharma authored and Jonathan Leibiusky committed Dec 26, 2010
1 parent f1379ff commit 0999132
Show file tree
Hide file tree
Showing 5 changed files with 325 additions and 20 deletions.
55 changes: 55 additions & 0 deletions src/main/java/redis/clients/johm/JOhmUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
Expand Down Expand Up @@ -138,6 +139,28 @@ static boolean detectJOhmCollection(final Field field) {
return isJOhmCollection;
}

public static JOhmCollectionDataType detectJOhmCollectionDataType(
final Class<?> dataClazz) {
JOhmCollectionDataType type = null;
if (Validator.checkSupportedPrimitiveClazz(dataClazz)) {
type = JOhmCollectionDataType.PRIMITIVE;
} else {
try {
Validator.checkValidModelClazz(dataClazz);
type = JOhmCollectionDataType.MODEL;
} catch (JOhmException exception) {
// drop it
}
}

if (type == null) {
throw new JOhmException(dataClazz.getSimpleName()
+ " is not a supported JOhm Collection Data Type");
}

return type;
}

@SuppressWarnings("unchecked")
public static boolean isNullOrEmpty(final Object obj) {
if (obj == null) {
Expand Down Expand Up @@ -166,6 +189,10 @@ static List<Field> gatherAllFields(Class<?> clazz) {
return Collections.unmodifiableList(allFields);
}

public static enum JOhmCollectionDataType {
PRIMITIVE, MODEL;
}

public final static class Convertor {
static Object convert(final Field field, final String value) {
return convert(field.getType(), value);
Expand Down Expand Up @@ -409,5 +436,33 @@ static void checkAttributeReferenceIndexRules(final Field field) {
checkValidReference(field);
}
}

public static boolean checkSupportedPrimitiveClazz(
final Class<?> primitiveClazz) {
return JOHM_SUPPORTED_PRIMITIVES.contains(primitiveClazz);
}
}

private static final Set<Class<?>> JOHM_SUPPORTED_PRIMITIVES = new HashSet<Class<?>>();
static {
JOHM_SUPPORTED_PRIMITIVES.add(String.class);
JOHM_SUPPORTED_PRIMITIVES.add(Byte.class);
JOHM_SUPPORTED_PRIMITIVES.add(byte.class);
JOHM_SUPPORTED_PRIMITIVES.add(Character.class);
JOHM_SUPPORTED_PRIMITIVES.add(char.class);
JOHM_SUPPORTED_PRIMITIVES.add(Short.class);
JOHM_SUPPORTED_PRIMITIVES.add(short.class);
JOHM_SUPPORTED_PRIMITIVES.add(Integer.class);
JOHM_SUPPORTED_PRIMITIVES.add(int.class);
JOHM_SUPPORTED_PRIMITIVES.add(Float.class);
JOHM_SUPPORTED_PRIMITIVES.add(float.class);
JOHM_SUPPORTED_PRIMITIVES.add(Double.class);
JOHM_SUPPORTED_PRIMITIVES.add(double.class);
JOHM_SUPPORTED_PRIMITIVES.add(Long.class);
JOHM_SUPPORTED_PRIMITIVES.add(long.class);
JOHM_SUPPORTED_PRIMITIVES.add(Boolean.class);
JOHM_SUPPORTED_PRIMITIVES.add(boolean.class);
JOHM_SUPPORTED_PRIMITIVES.add(BigDecimal.class);
JOHM_SUPPORTED_PRIMITIVES.add(BigInteger.class);
}
}
114 changes: 99 additions & 15 deletions src/main/java/redis/clients/johm/collections/RedisMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,24 @@
import redis.clients.johm.JOhm;
import redis.clients.johm.JOhmUtils;
import redis.clients.johm.Nest;
import redis.clients.johm.JOhmUtils.Convertor;
import redis.clients.johm.JOhmUtils.JOhmCollectionDataType;

/**
* RedisMap is a JOhm-internal Map implementation to serve as a proxy for the
* Redis persisted hash and provide lazy-loading semantics to minimize datastore
* network traffic. It does a best-effort job of minimizing hash staleness but
* does so without any locking and is not thread-safe. Only add and remove
* operations trigger a remote-sync of local internal storage.
*
* RedisMap does not support null keys or values.
*/
public class RedisMap<K, V> implements Map<K, V> {
private final Nest<? extends V> nest;
private final Class<? extends K> keyClazz;
private final Class<? extends V> valueClazz;
private final JOhmCollectionDataType johmKeyType;
private final JOhmCollectionDataType johmValueType;
private final Field field;
private final Object owner;

Expand All @@ -32,22 +38,34 @@ public RedisMap(final Class<? extends K> keyClazz,
Field field, Object owner) {
this.keyClazz = keyClazz;
this.valueClazz = valueClazz;
johmKeyType = JOhmUtils.detectJOhmCollectionDataType(keyClazz);
johmValueType = JOhmUtils.detectJOhmCollectionDataType(valueClazz);
this.nest = nest;
this.field = field;
this.owner = owner;
}

private void indexValue(K element) {
if (field.isAnnotationPresent(Indexed.class)) {
nest.cat(field.getName()).cat(element).sadd(
JOhmUtils.getId(owner).toString());
if (johmKeyType == JOhmCollectionDataType.PRIMITIVE) {
nest.cat(field.getName()).cat(element).sadd(
JOhmUtils.getId(owner).toString());
} else if (johmKeyType == JOhmCollectionDataType.MODEL) {
nest.cat(field.getName()).cat(JOhmUtils.getId(element)).sadd(
JOhmUtils.getId(owner).toString());
}
}
}

private void unindexValue(K element) {
if (field.isAnnotationPresent(Indexed.class)) {
nest.cat(field.getName()).cat(element).srem(
JOhmUtils.getId(owner).toString());
if (johmKeyType == JOhmCollectionDataType.PRIMITIVE) {
nest.cat(field.getName()).cat(element).srem(
JOhmUtils.getId(owner).toString());
} else if (johmKeyType == JOhmCollectionDataType.MODEL) {
nest.cat(field.getName()).cat(JOhmUtils.getId(element)).srem(
JOhmUtils.getId(owner).toString());
}
}
}

Expand Down Expand Up @@ -77,13 +95,25 @@ public Set<java.util.Map.Entry<K, V>> entrySet() {
return scrollElements().entrySet();
}

@SuppressWarnings("unchecked")
@Override
public V get(Object key) {
V value = null;
String valueKey = nest.cat(JOhmUtils.getId(owner)).cat(field.getName())
.hget(key.toString());
String valueKey = null;
if (johmKeyType == JOhmCollectionDataType.PRIMITIVE) {
valueKey = nest.cat(JOhmUtils.getId(owner)).cat(field.getName())
.hget(key.toString());
} else if (johmKeyType == JOhmCollectionDataType.MODEL) {
valueKey = nest.cat(JOhmUtils.getId(owner)).cat(field.getName())
.hget(JOhmUtils.getId(key).toString());
}

if (!JOhmUtils.isNullOrEmpty(valueKey)) {
value = JOhm.<V>get(valueClazz, Integer.parseInt(valueKey));
if (johmValueType == JOhmCollectionDataType.PRIMITIVE) {
value = (V) Convertor.convert(valueClazz, valueKey);
} else if (johmValueType == JOhmCollectionDataType.MODEL) {
value = JOhm.<V> get(valueClazz, Integer.parseInt(valueKey));
}
}
return value;
}
Expand All @@ -99,7 +129,11 @@ public Set<K> keySet() {
Set<K> keys = new LinkedHashSet<K>();
for (String key : nest.cat(JOhmUtils.getId(owner)).cat(field.getName())
.hkeys()) {
keys.add((K) JOhmUtils.Convertor.convert(keyClazz, key));
if (johmKeyType == JOhmCollectionDataType.PRIMITIVE) {
keys.add((K) JOhmUtils.Convertor.convert(keyClazz, key));
} else if (johmKeyType == JOhmCollectionDataType.MODEL) {
keys.add(JOhm.<K> get(keyClazz, Integer.parseInt(key)));
}
}
return keys;
}
Expand All @@ -122,8 +156,13 @@ public void putAll(Map<? extends K, ? extends V> mapToCopyIn) {
@Override
public V remove(Object key) {
V value = get(key);
nest.cat(JOhmUtils.getId(owner)).cat(field.getName()).hdel(
key.toString());
if (johmKeyType == JOhmCollectionDataType.PRIMITIVE) {
nest.cat(JOhmUtils.getId(owner)).cat(field.getName()).hdel(
key.toString());
} else if (johmKeyType == JOhmCollectionDataType.MODEL) {
nest.cat(JOhmUtils.getId(owner)).cat(field.getName()).hdel(
JOhmUtils.getId(key).toString());
}
unindexValue((K) key);
return value;
}
Expand All @@ -142,7 +181,27 @@ public Collection<V> values() {

private V internalPut(final K key, final V value) {
Map<String, String> hash = new LinkedHashMap<String, String>();
hash.put(key.toString(), JOhmUtils.getId(value).toString());
String keyString = null;
String valueString = null;
if (johmKeyType == JOhmCollectionDataType.PRIMITIVE
&& johmValueType == JOhmCollectionDataType.PRIMITIVE) {
keyString = key.toString();
valueString = value.toString();
} else if (johmKeyType == JOhmCollectionDataType.PRIMITIVE
&& johmValueType == JOhmCollectionDataType.MODEL) {
keyString = key.toString();
valueString = JOhmUtils.getId(value).toString();
} else if (johmKeyType == JOhmCollectionDataType.MODEL
&& johmValueType == JOhmCollectionDataType.PRIMITIVE) {
keyString = JOhmUtils.getId(key).toString();
valueString = value.toString();
} else if (johmKeyType == JOhmCollectionDataType.MODEL
&& johmValueType == JOhmCollectionDataType.MODEL) {
keyString = JOhmUtils.getId(key).toString();
valueString = JOhmUtils.getId(value).toString();
}

hash.put(keyString, valueString);
nest.cat(JOhmUtils.getId(owner)).cat(field.getName()).hmset(hash);
indexValue(key);
return value;
Expand All @@ -153,13 +212,38 @@ private synchronized Map<K, V> scrollElements() {
Map<String, String> savedHash = nest.cat(JOhmUtils.getId(owner)).cat(
field.getName()).hgetAll();
Map<K, V> backingMap = new HashMap<K, V>();
K savedKey = null;
V savedValue = null;
for (Map.Entry<String, String> entry : savedHash.entrySet()) {
K savedKey = (K) JOhmUtils.Convertor.convert(keyClazz, entry
.getKey());
V savedValue = JOhm.<V>get(valueClazz, Integer.parseInt(entry
.getValue()));
if (johmKeyType == JOhmCollectionDataType.PRIMITIVE
&& johmValueType == JOhmCollectionDataType.PRIMITIVE) {
savedKey = (K) JOhmUtils.Convertor.convert(keyClazz, entry
.getKey());
savedValue = (V) JOhmUtils.Convertor.convert(valueClazz, entry
.getValue());
} else if (johmKeyType == JOhmCollectionDataType.PRIMITIVE
&& johmValueType == JOhmCollectionDataType.MODEL) {
savedKey = (K) JOhmUtils.Convertor.convert(keyClazz, entry
.getKey());
savedValue = JOhm.<V> get(valueClazz, Integer.parseInt(entry
.getValue()));
} else if (johmKeyType == JOhmCollectionDataType.MODEL
&& johmValueType == JOhmCollectionDataType.PRIMITIVE) {
savedKey = JOhm.<K> get(keyClazz, Integer.parseInt(entry
.getKey()));
savedValue = (V) JOhmUtils.Convertor.convert(valueClazz, entry
.getValue());
} else if (johmKeyType == JOhmCollectionDataType.MODEL
&& johmValueType == JOhmCollectionDataType.MODEL) {
savedKey = JOhm.<K> get(keyClazz, Integer.parseInt(entry
.getKey()));
savedValue = JOhm.<V> get(valueClazz, Integer.parseInt(entry
.getValue()));
}

backingMap.put(savedKey, savedValue);
}

return backingMap;
}
}
95 changes: 95 additions & 0 deletions src/test/java/redis/clients/johm/CollectionsDataTypeTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package redis.clients.johm;

import org.junit.Test;

import redis.clients.johm.models.Country;
import redis.clients.johm.models.Distribution;
import redis.clients.johm.models.User;

public class CollectionsDataTypeTest extends JOhmTestBase {
@Test
public void testMapDataTypeCombinations() {
Distribution distro = new Distribution();
distro.setDistroScope("World");
JOhm.save(distro);

// K = Primitive, V = Primitive
distro.getAgeNameDistribution().put(10, "John");
distro.getAgeNameDistribution().put(35, "Doe");

Distribution savedDistro = JOhm.get(Distribution.class, distro.getId());
assertEquals(2, savedDistro.getAgeNameDistribution().size());

assertEquals("John", savedDistro.getAgeNameDistribution().get(10));
assertEquals("Doe", savedDistro.getAgeNameDistribution().get(35));

distro.getAgeNameDistribution().remove(10);
distro.getAgeNameDistribution().remove(35);
savedDistro = JOhm.get(Distribution.class, distro.getId());
assertEquals(0, savedDistro.getAgeNameDistribution().size());

// K = Model, V = Primitive
Country country1 = new Country();
country1.setName("FriendlyCountry");
JOhm.save(country1);
Country country2 = new Country();
country2.setName("AngryCountry");
JOhm.save(country2);

distro.getCountryAverageAgeDistribution().put(country1, 30);
distro.getCountryAverageAgeDistribution().put(country2, 90);
savedDistro = JOhm.get(Distribution.class, distro.getId());
assertEquals(2, savedDistro.getCountryAverageAgeDistribution().size());

assertTrue(30 == savedDistro.getCountryAverageAgeDistribution().get(
country1));
assertTrue(90 == savedDistro.getCountryAverageAgeDistribution().get(
country2));

distro.getCountryAverageAgeDistribution().remove(country1);
distro.getCountryAverageAgeDistribution().remove(country2);
savedDistro = JOhm.get(Distribution.class, distro.getId());
assertEquals(0, savedDistro.getCountryAverageAgeDistribution().size());

// K = Primitive, V = Model
distro.getNameCountryDistribution().put("John", country1);
distro.getNameCountryDistribution().put("Doe", country2);

savedDistro = JOhm.get(Distribution.class, distro.getId());
assertEquals(2, savedDistro.getNameCountryDistribution().size());

assertEquals(country1, savedDistro.getNameCountryDistribution().get(
"John"));
assertEquals(country2, savedDistro.getNameCountryDistribution().get(
"Doe"));

distro.getNameCountryDistribution().remove("John");
distro.getNameCountryDistribution().remove("Doe");
savedDistro = JOhm.get(Distribution.class, distro.getId());
assertEquals(0, savedDistro.getNameCountryDistribution().size());

// K = Model, V = Model
User user1 = new User();
user1.setName("Happy");
JOhm.save(user1);
User user2 = new User();
user2.setName("Frightened");
JOhm.save(user2);

distro.getUserCitizenshipDistribution().put(user1, country1);
distro.getUserCitizenshipDistribution().put(user2, country2);

savedDistro = JOhm.get(Distribution.class, distro.getId());
assertEquals(2, savedDistro.getUserCitizenshipDistribution().size());

assertEquals(country1, savedDistro.getUserCitizenshipDistribution()
.get(user1));
assertEquals(country2, savedDistro.getUserCitizenshipDistribution()
.get(user2));

savedDistro.getUserCitizenshipDistribution().remove(user1);
savedDistro.getUserCitizenshipDistribution().remove(user2);
assertEquals(0, savedDistro.getUserCitizenshipDistribution().size());
}

}
Loading

0 comments on commit 0999132

Please sign in to comment.