Skip to content

Commit 4dc09b5

Browse files
committed
Initial pass of complex CDT operations. Fixed a bug with constructors
with a key of a primitive type being mapped from a map not working correctly
1 parent 17971af commit 4dc09b5

File tree

8 files changed

+389
-38
lines changed

8 files changed

+389
-38
lines changed

src/main/java/com/aerospike/mapper/tools/AeroMapper.java

Lines changed: 112 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import java.util.ArrayList;
66
import java.util.List;
77
import java.util.Map;
8+
import java.util.Map.Entry;
89
import java.util.function.Function;
910

1011
import javax.validation.constraints.NotNull;
@@ -15,8 +16,15 @@
1516
import com.aerospike.client.Bin;
1617
import com.aerospike.client.IAerospikeClient;
1718
import com.aerospike.client.Key;
19+
import com.aerospike.client.Operation;
1820
import com.aerospike.client.Record;
1921
import com.aerospike.client.Value;
22+
import com.aerospike.client.cdt.ListOperation;
23+
import com.aerospike.client.cdt.ListReturnType;
24+
import com.aerospike.client.cdt.MapOperation;
25+
import com.aerospike.client.cdt.MapOrder;
26+
import com.aerospike.client.cdt.MapPolicy;
27+
import com.aerospike.client.cdt.MapReturnType;
2028
import com.aerospike.client.policy.BatchPolicy;
2129
import com.aerospike.client.policy.Policy;
2230
import com.aerospike.client.policy.QueryPolicy;
@@ -25,9 +33,13 @@
2533
import com.aerospike.client.policy.WritePolicy;
2634
import com.aerospike.client.query.RecordSet;
2735
import com.aerospike.client.query.Statement;
36+
import com.aerospike.mapper.annotations.AerospikeEmbed;
37+
import com.aerospike.mapper.annotations.AerospikeEmbed.EmbedType;
2838
import com.aerospike.mapper.tools.ClassCache.PolicyType;
39+
import com.aerospike.mapper.tools.TypeUtils.AnnotatedType;
2940
import com.aerospike.mapper.tools.configuration.ClassConfig;
3041
import com.aerospike.mapper.tools.configuration.Configuration;
42+
import com.aerospike.mapper.tools.mappers.ListMapper;
3143
import com.fasterxml.jackson.core.JsonParseException;
3244
import com.fasterxml.jackson.core.JsonProcessingException;
3345
import com.fasterxml.jackson.databind.JsonMappingException;
@@ -335,7 +347,107 @@ public boolean delete(WritePolicy writePolicy, @NotNull Object object) throws Ae
335347
}
336348
return mClient.delete(writePolicy, key);
337349
}
350+
351+
public <T> VirtualList<T> asBackedList(@NotNull Object object, @NotNull String binName, Class<T> clazz) {
352+
return new VirtualList<T>(this, object, binName, clazz);
353+
}
354+
355+
public static class VirtualList<E> {
356+
private final AeroMapper mapper;
357+
private final ValueType value;
358+
private final ClassCacheEntry<?> owningEntry;
359+
private final ClassCacheEntry<?> elementEntry;
360+
private final String binName;
361+
private final ListMapper listMapper;
362+
private final Key key;
363+
private final EmbedType listType;
364+
private final EmbedType elementType;
365+
366+
public VirtualList(@NotNull AeroMapper mapper, @NotNull Object object, @NotNull String binName, @NotNull Class<E> clazz) {
367+
Class<?> owningClazz = object.getClass();
368+
this.owningEntry = (ClassCacheEntry<?>) ClassCache.getInstance().loadClass(owningClazz, mapper);
369+
this.elementEntry = (ClassCacheEntry<?>) ClassCache.getInstance().loadClass(clazz, mapper);
370+
this.mapper = mapper;
371+
this.binName = binName;
372+
this.value = owningEntry.getValueFromBinName(binName);
373+
if (value == null) {
374+
throw new AerospikeException(String.format("Class %s has no bin called %s", clazz.getSimpleName(), binName));
375+
}
376+
String set = owningEntry.getSetName();
377+
if ("".equals(set)) {
378+
// Use the null set
379+
set = null;
380+
}
381+
key = new Key(owningEntry.getNamespace(), set, Value.get(owningEntry.getKey(object)));
382+
383+
AnnotatedType annotatedType = value.getAnnotatedType();
384+
AerospikeEmbed embed = annotatedType.getAnnotation(AerospikeEmbed.class);
385+
if (embed == null) {
386+
throw new AerospikeException(String.format("Bin %s on class %s is not specified as a embedded", binName, clazz.getSimpleName()));
387+
}
388+
listType = embed.type() == EmbedType.DEFAULT ? EmbedType.LIST : embed.type();
389+
elementType = embed.elementType() == EmbedType.DEFAULT ? EmbedType.MAP : embed.elementType();
390+
Class<?> binClazz = value.getType();
391+
if (!(binClazz.isArray() || (Map.class.isAssignableFrom(binClazz)) || List.class.isAssignableFrom(binClazz))) {
392+
throw new AerospikeException(String.format("Bin %s on class %s is not a collection class", binName, clazz.getSimpleName()));
393+
}
394+
395+
TypeMapper typeMapper = value.getTypeMapper();
396+
if (typeMapper instanceof ListMapper) {
397+
listMapper = ((ListMapper)typeMapper);
398+
}
399+
else {
400+
throw new AerospikeException(String.format("Bin %s on class %s is not mapped via a listMapper. This is unexpected", binName, clazz.getSimpleName()));
401+
}
402+
}
403+
404+
public VirtualList<E> keptInSync(boolean inSync) {
405+
return this;
406+
}
407+
338408

409+
private Operation getAppendOperation(Object aerospikeObject) {
410+
if (aerospikeObject instanceof Entry) {
411+
Entry<Object, Object> entry = (Entry) aerospikeObject;
412+
return MapOperation.put(new MapPolicy(MapOrder.KEY_ORDERED, 0), binName, Value.get(entry.getKey()), Value.get(entry.getValue()));
413+
}
414+
else {
415+
return ListOperation.append(binName, Value.get(aerospikeObject));
416+
}
417+
}
418+
public VirtualList<E> append(E element) {
419+
return this.append(null, element);
420+
}
421+
422+
public VirtualList<E> append(WritePolicy writePolicy, E element) {
423+
Object result = listMapper.toAerospikeInstanceFormat(element);
424+
if (writePolicy == null) {
425+
writePolicy = new WritePolicy(owningEntry.getWritePolicy());
426+
writePolicy.recordExistsAction = RecordExistsAction.UPDATE;
427+
}
428+
this.mapper.mClient.operate(writePolicy, key, getAppendOperation(result));
429+
return this;
430+
}
431+
432+
public E get(int index) {
433+
// TODO
434+
//Object aerospikeKey = elementEntry.translateKeyToAerospikeKey(key);
435+
Operation operation;
436+
if (listType == EmbedType.LIST) {
437+
operation = ListOperation.getByIndex(binName, index, ListReturnType.VALUE);
438+
}
439+
else {
440+
operation = MapOperation.getByIndex(binName, index, MapReturnType.KEY_VALUE);
441+
}
442+
Record record = this.mapper.mClient.operate(null, key, operation);
443+
List<Object> list = (List<Object>) record.getList(binName);
444+
Object result = listMapper.fromAerospikeInstanceFormat(list.get(0));
445+
446+
return (E) result;
447+
}
448+
}
449+
450+
339451
public <T> void find(@NotNull Class<T> clazz, Function<T, Boolean> function) throws AerospikeException {
340452
ClassCacheEntry<T> entry = getEntryAndValidateNamespace(clazz);
341453

@@ -362,7 +474,6 @@ public <T> void find(@NotNull Class<T> clazz, Function<T, Boolean> function) thr
362474
recordSet.close();
363475
}
364476
}
365-
366477
}
367478

368479
// --------------------------------------------------------------------------------------------------

src/main/java/com/aerospike/mapper/tools/ClassCacheEntry.java

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ public class ClassCacheEntry<T> {
7171
private final QueryPolicy queryPolicy;
7272
private final ScanPolicy scanPolicy;
7373
private String[] constructorParamBins;
74+
private Object[] constructorParamDefaults;
7475
private Constructor<T> constructor;
7576
/**
7677
* When there are subclasses, we need to store the type information to be able to re-create an instance of the same type. As the
@@ -350,6 +351,7 @@ private void findConstructor() {
350351

351352
Parameter[] params = desiredConstructor.getParameters();
352353
this.constructorParamBins = new String[params.length];
354+
this.constructorParamDefaults = new Object[params.length];
353355

354356
int count = 0;
355357
for (Parameter thisParam : params) {
@@ -373,6 +375,7 @@ private void findConstructor() {
373375
" is of type " + type + " but assigned from bin \"" + binName + "\" of type " +values.get(binName).getType()+ ". These types are incompatible.");
374376
}
375377
constructorParamBins[count-1] = binName;
378+
constructorParamDefaults[count-1] = PrimitiveDefaults.getDefaultValue(thisParam.getType());
376379
}
377380
this.constructor = (Constructor<T>) desiredConstructor;
378381
this.constructor.setAccessible(true);
@@ -447,17 +450,19 @@ private void loadPropertiesFromClass(@NotNull Class<?> clazz, ClassConfig config
447450
if (key != null) {
448451
throw new AerospikeException("Class " + clazz.getName() + " cannot have a more than one key");
449452
}
450-
TypeMapper typeMapper = TypeUtils.getMapper(keyProperty.getType(), new AnnotatedType(config, keyProperty.getGetter()), this.mapper);
451-
this.key = new ValueType.MethodValue(keyProperty, typeMapper);
453+
AnnotatedType annotatedType = new AnnotatedType(config, keyProperty.getGetter());
454+
TypeMapper typeMapper = TypeUtils.getMapper(keyProperty.getType(), annotatedType, this.mapper);
455+
this.key = new ValueType.MethodValue(keyProperty, typeMapper, annotatedType);
452456
}
453457
for (String thisPropertyName : properties.keySet()) {
454458
PropertyDefinition thisProperty = properties.get(thisPropertyName);
455459
thisProperty.validate(clazz.getName(), config, false);
456460
if (this.values.get(thisPropertyName) != null) {
457461
throw new AerospikeException("Class " + clazz.getName() + " cannot define the mapped name " + thisPropertyName + " more than once");
458462
}
459-
TypeMapper typeMapper = TypeUtils.getMapper(thisProperty.getType(), new AnnotatedType(config, thisProperty.getGetter()), this.mapper);
460-
ValueType value = new ValueType.MethodValue(thisProperty, typeMapper);
463+
AnnotatedType annotatedType = new AnnotatedType(config, thisProperty.getGetter());
464+
TypeMapper typeMapper = TypeUtils.getMapper(thisProperty.getType(), annotatedType, this.mapper);
465+
ValueType value = new ValueType.MethodValue(thisProperty, typeMapper, annotatedType);
461466
values.put(thisPropertyName, value);
462467
}
463468
}
@@ -475,8 +480,9 @@ private void loadFieldsFromClass(Class<?> clazz, boolean mapAll, ClassConfig con
475480
if (key != null) {
476481
throw new AerospikeException("Class " + clazz.getName() + " cannot have a more than one key");
477482
}
478-
TypeMapper typeMapper = TypeUtils.getMapper(thisField.getType(), new AnnotatedType(config, thisField), this.mapper);
479-
this.key = new ValueType.FieldValue(thisField, typeMapper);
483+
AnnotatedType annotatedType = new AnnotatedType(config, thisField);
484+
TypeMapper typeMapper = TypeUtils.getMapper(thisField.getType(), annotatedType, this.mapper);
485+
this.key = new ValueType.FieldValue(thisField, typeMapper, annotatedType);
480486
isKey = true;
481487
}
482488

@@ -507,8 +513,9 @@ private void loadFieldsFromClass(Class<?> clazz, boolean mapAll, ClassConfig con
507513
if (this.values.get(name) != null) {
508514
throw new AerospikeException("Class " + clazz.getName() + " cannot define the mapped name " + name + " more than once");
509515
}
510-
TypeMapper typeMapper = TypeUtils.getMapper(thisField.getType(), new AnnotatedType(config, thisField), this.mapper);
511-
ValueType valueType = new ValueType.FieldValue(thisField, typeMapper);
516+
AnnotatedType annotatedType = new AnnotatedType(config, thisField);
517+
TypeMapper typeMapper = TypeUtils.getMapper(thisField.getType(), annotatedType, this.mapper);
518+
ValueType valueType = new ValueType.FieldValue(thisField, typeMapper, annotatedType);
512519
values.put(name, valueType);
513520
}
514521
}
@@ -815,7 +822,12 @@ private T constructAndHydrate(Map<String, Object> javaValuesMap) throws Reflecti
815822
else {
816823
Object[] args = new Object[constructorParamBins.length];
817824
for (int i = 0; i < constructorParamBins.length; i++) {
818-
args[i] = javaValuesMap.get(constructorParamBins[i]);;
825+
if (javaValuesMap.containsKey(constructorParamBins[i])) {
826+
args[i] = javaValuesMap.get(constructorParamBins[i]);
827+
}
828+
else {
829+
args[i] = constructorParamDefaults[i];
830+
}
819831
javaValuesMap.remove(constructorParamBins[i]);
820832
}
821833
result = constructor.newInstance(args);
@@ -931,6 +943,11 @@ public void hydrateFromList(List<Object> list, Object instance, boolean skipKey)
931943
}
932944
}
933945

946+
// package visible
947+
ValueType getValueFromBinName(String name) {
948+
return this.values.get(name);
949+
}
950+
934951
@Override
935952
public String toString() {
936953
return String.format("ClassCacheEntry<%s> (ns=%s, set=%s,subclass=%b,shortName=%s)", this.getUnderlyingClass().getSimpleName(), this.namespace, this.setName, this.isChildClass, this.shortenedClassName);
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.aerospike.mapper.tools;
2+
3+
public class PrimitiveDefaults {
4+
// These gets initialized to their default values
5+
private static boolean DEFAULT_BOOLEAN;
6+
private static byte DEFAULT_BYTE;
7+
private static short DEFAULT_SHORT;
8+
private static int DEFAULT_INT;
9+
private static long DEFAULT_LONG;
10+
private static float DEFAULT_FLOAT;
11+
private static double DEFAULT_DOUBLE;
12+
13+
public static Object getDefaultValue(Class<?> clazz) {
14+
if (clazz.equals(boolean.class)) {
15+
return DEFAULT_BOOLEAN;
16+
} else if (clazz.equals(byte.class)) {
17+
return DEFAULT_BYTE;
18+
} else if (clazz.equals(short.class)) {
19+
return DEFAULT_SHORT;
20+
} else if (clazz.equals(int.class)) {
21+
return DEFAULT_INT;
22+
} else if (clazz.equals(long.class)) {
23+
return DEFAULT_LONG;
24+
} else if (clazz.equals(float.class)) {
25+
return DEFAULT_FLOAT;
26+
} else if (clazz.equals(double.class)) {
27+
return DEFAULT_DOUBLE;
28+
} else {
29+
return null;
30+
}
31+
}
32+
}

src/main/java/com/aerospike/mapper/tools/TypeUtils.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,18 @@ public ParameterizedType getParamterizedType() {
8282
public boolean isParameterizedType() {
8383
return paramterizedType != null;
8484
}
85+
86+
public <T> T getAnnotation(Class<T> clazz) {
87+
if (this.annotations == null) {
88+
return null;
89+
}
90+
for (Annotation annotation : this.annotations) {
91+
if (annotation.annotationType().equals(clazz)) {
92+
return (T) annotation;
93+
}
94+
}
95+
return null;
96+
}
8597
}
8698

8799
// package visibility

src/main/java/com/aerospike/mapper/tools/ValueType.java

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import com.aerospike.client.AerospikeException;
99
import com.aerospike.client.Key;
1010
import com.aerospike.mapper.annotations.AerospikeVersion;
11+
import com.aerospike.mapper.tools.TypeUtils.AnnotatedType;
1112

1213
/**
1314
* Implementation of a value, which can be either a method on a class (getter) or a field
@@ -17,9 +18,11 @@ public abstract class ValueType {
1718
private int minimumVersion = 1;
1819
private int maximumVersion = Integer.MAX_VALUE;
1920
private final TypeMapper mapper;
21+
private final AnnotatedType annotatedType;
2022

21-
public ValueType(@NotNull final TypeMapper mapper) {
23+
public ValueType(@NotNull final TypeMapper mapper, final AnnotatedType annotatedType) {
2224
this.mapper = mapper;
25+
this.annotatedType = annotatedType;
2326
}
2427
public abstract Object get(Object obj) throws ReflectiveOperationException;
2528
public abstract void set(Object obj, Object value) throws ReflectiveOperationException;
@@ -43,11 +46,15 @@ public TypeMapper getTypeMapper() {
4346
return this.mapper;
4447
}
4548

49+
public AnnotatedType getAnnotatedType() {
50+
return annotatedType;
51+
}
52+
4653

4754
public static class FieldValue extends ValueType {
4855
private Field field;
49-
public FieldValue(Field field, TypeMapper typeMapper) {
50-
super(typeMapper);
56+
public FieldValue(Field field, TypeMapper typeMapper, AnnotatedType annotatedType) {
57+
super(typeMapper, annotatedType);
5158
this.field = field;
5259
this.field.setAccessible(true);
5360
if (this.field.isAnnotationPresent(AerospikeVersion.class)) {
@@ -79,8 +86,8 @@ public Annotation[] getAnnotations() {
7986
public static class MethodValue extends ValueType {
8087
private PropertyDefinition property;
8188

82-
public MethodValue(PropertyDefinition property, TypeMapper typeMapper) {
83-
super(typeMapper);
89+
public MethodValue(PropertyDefinition property, TypeMapper typeMapper, AnnotatedType annotatedType) {
90+
super(typeMapper, annotatedType);
8491
this.property = property;
8592
}
8693

0 commit comments

Comments
 (0)