Skip to content

Commit 92303e0

Browse files
committed
Fix bugs, improve safety, further decouple modules, add tests
1 parent a60dd85 commit 92303e0

24 files changed

Lines changed: 721 additions & 167 deletions

File tree

.editorconfig

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# top-most EditorConfig file
2+
root = true
3+
4+
# Unix-style newlines with a newline ending every file
5+
[*]
6+
charset = utf-8
7+
end_of_line = lf
8+
9+
[*.java]
10+
indent_style = space
11+
indent_size = 4
12+
insert_final_newline = true
13+
max_line_length = 120
14+
ij_java_wrap_long_lines = true
15+
ij_java_wrap_comments = true
16+
ij_java_method_call_chain_wrap = normal
17+
ij_java_blank_lines_after_class_header = 1
18+
ij_java_class_count_to_use_import_on_demand = 10
19+
ij_java_names_count_to_use_import_on_demand = 10

core/src/main/java/com/aerospike/mapper/tools/ClassCache.java

Lines changed: 12 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,15 @@
88
import lombok.Getter;
99

1010
import javax.validation.constraints.NotNull;
11-
import java.util.HashMap;
12-
import java.util.Map;
11+
import java.util.concurrent.ConcurrentHashMap;
1312

1413
public class ClassCache {
1514

1615
@Getter
1716
private static final ClassCache instance = new ClassCache();
18-
private final Map<Class<?>, ClassCacheEntry<?>> cacheMap = new HashMap<>();
19-
private final Map<String, ClassConfig> classesConfig = new HashMap<>();
20-
private final Map<String, ClassCacheEntry<?>> storedNameToCacheEntry = new HashMap<>();
17+
private final ConcurrentHashMap<Class<?>, ClassCacheEntry<?>> cacheMap = new ConcurrentHashMap<>();
18+
private final ConcurrentHashMap<String, ClassConfig> classesConfig = new ConcurrentHashMap<>();
19+
private final ConcurrentHashMap<String, ClassCacheEntry<?>> storedNameToCacheEntry = new ConcurrentHashMap<>();
2120
private final Object lock = new Object();
2221

2322
private ClassCache() {
@@ -29,7 +28,6 @@ public <T> ClassCacheEntry<T> loadClass(@NotNull Class<T> clazz, IObjectMapper m
2928

3029
@SuppressWarnings("unchecked")
3130
public <T> ClassCacheEntry<T> loadClass(@NotNull Class<T> clazz, IObjectMapper mapper, boolean requireRecord) {
32-
// Clazz can be null if an interface is passed
3331
if (clazz == null || clazz.isPrimitive() || clazz.equals(Object.class) || clazz.equals(String.class)
3432
|| clazz.equals(Character.class) || Number.class.isAssignableFrom(clazz)) {
3533
return null;
@@ -41,14 +39,6 @@ public <T> ClassCacheEntry<T> loadClass(@NotNull Class<T> clazz, IObjectMapper m
4139
entry = (ClassCacheEntry<T>) cacheMap.get(clazz);
4240
if (entry == null) {
4341
try {
44-
// Construct a class cache entry. This must be done in 2 steps, one creating the entry
45-
// and the other finalizing construction of it.
46-
// This is to cater for classes which recursively refer to themselves, such as
47-
// public static class A {
48-
// @AerospikeKey
49-
// public int id;
50-
// public A a;
51-
// }
5242
entry = new ClassCacheEntry<>(clazz, mapper, getClassConfig(clazz), requireRecord);
5343
} catch (NotAnnotatedClass nae) {
5444
return null;
@@ -69,7 +59,6 @@ public <T> ClassCacheEntry<T> loadClass(@NotNull Class<T> clazz, IObjectMapper m
6959
return entry;
7060
}
7161

72-
// package visibility
7362
void setStoredName(@NotNull ClassCacheEntry<?> entry, @NotNull String name) {
7463
ClassCacheEntry<?> existingEntry = storedNameToCacheEntry.get(name);
7564
if (existingEntry != null && !(existingEntry.equals(entry))) {
@@ -89,14 +78,13 @@ public boolean hasClass(Class<?> clazz) {
8978
return cacheMap.containsKey(clazz);
9079
}
9180

92-
/**
93-
* This method is typically only used for testing
94-
*/
9581
public void clear() {
96-
this.cacheMap.clear();
97-
this.classesConfig.clear();
98-
TypeUtils.clear();
99-
this.storedNameToCacheEntry.clear();
82+
synchronized (lock) {
83+
this.cacheMap.clear();
84+
this.classesConfig.clear();
85+
TypeUtils.clear();
86+
this.storedNameToCacheEntry.clear();
87+
}
10088
}
10189

10290
public void addConfiguration(@NotNull Configuration configuration) {
@@ -105,6 +93,7 @@ public void addConfiguration(@NotNull Configuration configuration) {
10593
}
10694
}
10795

96+
@SuppressWarnings("unused")
10897
public ClassConfig getClassConfig(String className) {
10998
return classesConfig.get(className);
11099
}
@@ -113,19 +102,12 @@ public ClassConfig getClassConfig(Class<?> clazz) {
113102
return classesConfig.get(clazz.getName());
114103
}
115104

105+
@SuppressWarnings("unused")
116106
public boolean hasClassConfig(String className) {
117107
return classesConfig.containsKey(className);
118108
}
119109

120110
public boolean hasClassConfig(Class<?> clazz) {
121111
return classesConfig.containsKey(clazz.getName());
122112
}
123-
124-
public enum PolicyType {
125-
READ,
126-
WRITE,
127-
BATCH,
128-
SCAN,
129-
QUERY
130-
}
131113
}

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

Lines changed: 20 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ private enum FactoryMethodType {
113113
this.config = config;
114114
}
115115

116-
public ClassCacheEntry<T> construct() {
116+
public void construct() {
117117
if (config != null) {
118118
config.validate();
119119
this.overrideSettings(config);
@@ -146,7 +146,6 @@ public ClassCacheEntry<T> construct() {
146146

147147
this.checkRecordSettingsAgainstSuperClasses();
148148
constructed = true;
149-
return this;
150149
}
151150

152151
public boolean isNotConstructed() {
@@ -378,7 +377,7 @@ private Method findConstructorFactoryMethod() {
378377
String.format("Missing factoryClass definition when factoryMethod is specified on class %s",
379378
clazz.getSimpleName()));
380379
}
381-
if (StringUtils.isBlank(this.factoryClass)) {
380+
if (StringUtils.isBlank(this.factoryMethod)) {
382381
throw new AerospikeMapperException(
383382
String.format("Missing factoryMethod definition when factoryClass is specified on class %s",
384383
clazz.getSimpleName()));
@@ -806,21 +805,25 @@ private void mapField(Field thisField, BinConfig thisBin, boolean isKey) {
806805

807806
private Method findMethodWithNameAndParams(String name, Class<?>... params) {
808807
try {
809-
Method method = this.clazz.getDeclaredMethod(name, params);
810808
// TODO: Should this ascend the inheritance hierarchy using getMethod on superclasses?
811-
return method;
809+
return this.clazz.getDeclaredMethod(name, params);
812810
} catch (NoSuchMethodException nsme) {
813811
return null;
814812
}
815813
}
816814

817-
private Method findMethodWithNameAndParamByTypeName(String name, Class<?> firstParam, String secondParamTypeName) {
815+
/**
816+
* Searches for a 2-param setter whose second parameter is a recognized client Key or Value type, as determined by
817+
* the mapper's {@link SetterParamTypeResolver}.
818+
*/
819+
private Method findSetterWithClientParamType(String setterName, Class<?> firstParam) {
820+
SetterParamTypeResolver resolver = this.mapper.getSetterParamTypeResolver();
818821
for (Method m : this.clazz.getDeclaredMethods()) {
819-
if (m.getName().equals(name)) {
822+
if (m.getName().equals(setterName)) {
820823
Class<?>[] paramTypes = m.getParameterTypes();
821824
if (paramTypes.length == 2
822825
&& paramTypes[0].isAssignableFrom(firstParam)
823-
&& secondParamTypeName.equals(paramTypes[1].getName())) {
826+
&& resolver.resolve(paramTypes[1].getName()) != PropertyDefinition.SetterParamType.NONE) {
824827
return m;
825828
}
826829
}
@@ -844,10 +847,7 @@ private void validateAccessorsForField(String binName, Field thisField) {
844847

845848
Method setter = findMethodWithNameAndParams(setterName, thisField.getType());
846849
if (setter == null) {
847-
setter = findMethodWithNameAndParamByTypeName(setterName, thisField.getType(), "com.aerospike.client.Key");
848-
}
849-
if (setter == null) {
850-
setter = findMethodWithNameAndParamByTypeName(setterName, thisField.getType(), "com.aerospike.client.Value");
850+
setter = findSetterWithClientParamType(setterName, thisField.getType());
851851
}
852852
if (setter == null) {
853853
throw new AerospikeMapperException(String.format(
@@ -917,11 +917,6 @@ public Integer getTtl() {
917917
return ttl;
918918
}
919919

920-
/** Returns whether the key field is stored as a regular bin (true) or only in key metadata (false). */
921-
public boolean isKeyStoredAsBin() {
922-
return keyAsBin;
923-
}
924-
925920
/**
926921
* Traverses the class hierarchy to find the field name of the key (may be in a superclass).
927922
* Returns null if no key field is found.
@@ -976,21 +971,6 @@ public Integer getGenerationValue(Object instance) {
976971
}
977972
}
978973

979-
private boolean contains(String[] names, String thisName) {
980-
if (names == null || names.length == 0) {
981-
return true;
982-
}
983-
if (thisName == null) {
984-
return false;
985-
}
986-
for (String aName : names) {
987-
if (thisName.equals(aName)) {
988-
return true;
989-
}
990-
}
991-
return false;
992-
}
993-
994974
public Map<String, Object> getMap(Object instance, boolean needsType) {
995975
try {
996976
Map<String, Object> results = new HashMap<>();
@@ -1023,8 +1003,8 @@ private void addDataFromValueName(String name, Object instance, ClassCacheEntry<
10231003
}
10241004
}
10251005

1026-
private boolean isKeyField(String name) {
1027-
return keyName != null && keyName.equals(name);
1006+
private boolean isNotKeyField(String name) {
1007+
return keyName == null || !keyName.equals(name);
10281008
}
10291009

10301010
public List<Object> getList(Object instance, boolean skipKey, boolean needsType) {
@@ -1039,14 +1019,14 @@ public List<Object> getList(Object instance, boolean skipKey, boolean needsType)
10391019
if (thisClass.ordinals != null) {
10401020
for (int i = 1; i <= thisClass.ordinals.size(); i++) {
10411021
String name = thisClass.ordinals.get(i);
1042-
if (!skipKey || !isKeyField(name)) {
1022+
if (!skipKey || isNotKeyField(name)) {
10431023
addDataFromValueName(name, instance, thisClass, results);
10441024
}
10451025
}
10461026
}
10471027
for (String name : thisClass.values.keySet()) {
10481028
if (thisClass.fieldsWithOrdinals == null || !thisClass.fieldsWithOrdinals.contains(name)) {
1049-
if (!skipKey || !isKeyField(name)) {
1029+
if (!skipKey || isNotKeyField(name)) {
10501030
addDataFromValueName(name, instance, thisClass, results);
10511031
}
10521032
}
@@ -1237,15 +1217,15 @@ public T constructAndHydrate(List<Object> list, boolean skipKey) {
12371217
if (thisClass.ordinals != null) {
12381218
for (int i = 1; i <= thisClass.ordinals.size(); i++) {
12391219
String name = thisClass.ordinals.get(i);
1240-
if (!skipKey || !isKeyField(name)) {
1220+
if (!skipKey || isNotKeyField(name)) {
12411221
index = thisClass.setValueByField(name, objectVersion, recordVersion, null, index, list,
12421222
valueMap);
12431223
}
12441224
}
12451225
}
12461226
for (String name : thisClass.values.keySet()) {
12471227
if (thisClass.fieldsWithOrdinals == null || !thisClass.fieldsWithOrdinals.contains(name)) {
1248-
if (!skipKey || !isKeyField(name)) {
1228+
if (!skipKey || isNotKeyField(name)) {
12491229
index = thisClass.setValueByField(name, objectVersion, recordVersion, null, index, list,
12501230
valueMap);
12511231
}
@@ -1286,15 +1266,15 @@ public void hydrateFromList(List<Object> list, Object instance, boolean skipKey)
12861266
if (ordinals != null) {
12871267
for (int i = 1; i <= ordinals.size(); i++) {
12881268
String name = ordinals.get(i);
1289-
if (!skipKey || !isKeyField(name)) {
1269+
if (!skipKey || isNotKeyField(name)) {
12901270
index = setValueByField(name, objectVersion, recordVersion, instance, index, list,
12911271
null);
12921272
}
12931273
}
12941274
}
12951275
for (String name : this.values.keySet()) {
12961276
if (this.fieldsWithOrdinals == null || !thisClass.fieldsWithOrdinals.contains(name)) {
1297-
if (!skipKey || !isKeyField(name)) {
1277+
if (!skipKey || isNotKeyField(name)) {
12981278
index = setValueByField(name, objectVersion, recordVersion, instance, index, list,
12991279
null);
13001280
}

core/src/main/java/com/aerospike/mapper/tools/IObjectMapper.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,12 @@ public interface IObjectMapper {
88
IRecordConverter getMappingConverter();
99

1010
RecordLoader getRecordLoader();
11+
12+
/**
13+
* Returns a resolver that maps setter parameter type names to {@link PropertyDefinition.SetterParamType} values for
14+
* the client library used by this mapper.
15+
*/
16+
default SetterParamTypeResolver getSetterParamTypeResolver() {
17+
return SetterParamTypeResolver.DEFAULT;
18+
}
1119
}

core/src/main/java/com/aerospike/mapper/tools/PropertyDefinition.java

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import java.lang.annotation.Annotation;
1212
import java.lang.reflect.Method;
1313
import java.lang.reflect.Parameter;
14-
import java.lang.reflect.Type;
1514

1615
public class PropertyDefinition {
1716

@@ -50,10 +49,6 @@ public Annotation[] getAnnotations() {
5049
return getter != null ? getter.getAnnotations() : setter.getAnnotations();
5150
}
5251

53-
public Type getGenericType() {
54-
return this.getter.getGenericReturnType();
55-
}
56-
5752
/**
5853
* Validate that this is a valid property
5954
*/
@@ -79,11 +74,9 @@ public void validate(String className, ClassConfig config, boolean allowNoSetter
7974
if (setter.getParameterCount() == 2) {
8075
Parameter param = setter.getParameters()[1];
8176
String paramTypeName = param.getType().getName();
82-
if ("com.aerospike.client.Key".equals(paramTypeName)) {
83-
this.setterParamType = SetterParamType.KEY;
84-
} else if ("com.aerospike.client.Value".equals(paramTypeName)) {
85-
this.setterParamType = SetterParamType.VALUE;
86-
} else {
77+
SetterParamTypeResolver resolver = mapper.getSetterParamTypeResolver();
78+
this.setterParamType = resolver.resolve(paramTypeName);
79+
if (this.setterParamType == SetterParamType.NONE) {
8780
throw new AerospikeMapperException(String.format("Property %s on class %s has a setter with 2 arguments," +
8881
" but the second one is neither a Key nor a Value", this.name, className));
8982
}

core/src/main/java/com/aerospike/mapper/tools/RecordMetaConfig.java

Lines changed: 0 additions & 17 deletions
This file was deleted.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.aerospike.mapper.tools;
2+
3+
import com.aerospike.mapper.tools.PropertyDefinition.SetterParamType;
4+
5+
/**
6+
* Resolves the {@link SetterParamType} for a setter's second parameter based on the parameter's fully-qualified type
7+
* name. This decouples core from specific client libraries.
8+
*/
9+
@FunctionalInterface
10+
public interface SetterParamTypeResolver {
11+
12+
/**
13+
* Determines if the given parameter type name corresponds to a Key or Value type in the client library used by this
14+
* mapper.
15+
*
16+
* @param paramTypeName fully-qualified class name of the setter's second parameter
17+
* @return the resolved {@link SetterParamType}, or {@link SetterParamType#NONE} if unrecognized
18+
*/
19+
SetterParamType resolve(String paramTypeName);
20+
21+
/**
22+
* Default resolver that does not recognize any client-specific parameter types.
23+
*/
24+
SetterParamTypeResolver DEFAULT = paramTypeName -> SetterParamType.NONE;
25+
}

0 commit comments

Comments
 (0)