Skip to content

Commit 1d74e68

Browse files
Copilotanidotnet
andcommitted
Add FieldAccessHelper for interface property field access
- Created FieldAccessHelper to handle get/set operations on both regular and interface property fields - Modified RepositoryOperations to use FieldAccessHelper for field access - This fixes field access issues when using EntityDecorator with interface types - All existing tests still pass (1623 tests) Co-authored-by: anidotnet <[email protected]>
1 parent 3c66bd2 commit 1d74e68

File tree

2 files changed

+186
-9
lines changed

2 files changed

+186
-9
lines changed
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
/*
2+
* Copyright (c) 2017-2022 Nitrite author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
18+
package org.dizitart.no2.repository;
19+
20+
import java.lang.reflect.Field;
21+
import java.lang.reflect.Method;
22+
23+
/**
24+
* Helper class to access field values, handling both regular fields and interface properties.
25+
* For interface properties (synthetic fields from InterfacePropertyHolder), this class
26+
* finds and accesses the actual field on the concrete implementation object.
27+
*
28+
* @author Anindya Chatterjee
29+
* @since 4.3.2
30+
*/
31+
class FieldAccessHelper {
32+
33+
/**
34+
* Gets the value of a field from an object, handling both regular and interface property fields.
35+
*
36+
* @param field the field to access
37+
* @param obj the object to get the value from
38+
* @return the field value
39+
* @throws IllegalAccessException if field access fails
40+
*/
41+
static Object get(Field field, Object obj) throws IllegalAccessException {
42+
if (InterfacePropertyHolder.isInterfaceProperty(field)) {
43+
// This is a synthetic field for an interface property
44+
// Find and access the real field in the concrete object
45+
String propertyName = InterfacePropertyHolder.getPropertyName(field);
46+
return getPropertyValue(obj, propertyName);
47+
} else {
48+
field.setAccessible(true);
49+
return field.get(obj);
50+
}
51+
}
52+
53+
/**
54+
* Sets the value of a field on an object, handling both regular and interface property fields.
55+
*
56+
* @param field the field to set
57+
* @param obj the object to set the value on
58+
* @param value the value to set
59+
* @throws IllegalAccessException if field access fails
60+
*/
61+
static void set(Field field, Object obj, Object value) throws IllegalAccessException {
62+
if (InterfacePropertyHolder.isInterfaceProperty(field)) {
63+
// This is a synthetic field for an interface property
64+
// Find and set the real field in the concrete object
65+
String propertyName = InterfacePropertyHolder.getPropertyName(field);
66+
setPropertyValue(obj, propertyName, value);
67+
} else {
68+
field.setAccessible(true);
69+
field.set(obj, value);
70+
}
71+
}
72+
73+
/**
74+
* Gets a property value from an object, trying both field access and getter method.
75+
*/
76+
private static Object getPropertyValue(Object obj, String propertyName) throws IllegalAccessException {
77+
// Try to find the field in the object's class
78+
Field realField = findFieldInHierarchy(obj.getClass(), propertyName);
79+
if (realField != null) {
80+
realField.setAccessible(true);
81+
return realField.get(obj);
82+
}
83+
84+
// Fall back to getter method
85+
try {
86+
String getterName = "get" + Character.toUpperCase(propertyName.charAt(0));
87+
if (propertyName.length() > 1) {
88+
getterName += propertyName.substring(1);
89+
}
90+
Method getter = obj.getClass().getMethod(getterName);
91+
getter.setAccessible(true);
92+
return getter.invoke(obj);
93+
} catch (Exception e) {
94+
throw new IllegalAccessException("Cannot access property '" + propertyName + "' on " + obj.getClass().getName());
95+
}
96+
}
97+
98+
/**
99+
* Sets a property value on an object, trying both field access and setter method.
100+
*/
101+
private static void setPropertyValue(Object obj, String propertyName, Object value) throws IllegalAccessException {
102+
// Try to find the field in the object's class
103+
Field realField = findFieldInHierarchy(obj.getClass(), propertyName);
104+
if (realField != null) {
105+
realField.setAccessible(true);
106+
realField.set(obj, value);
107+
return;
108+
}
109+
110+
// Fall back to setter method
111+
try {
112+
String setterName = "set" + Character.toUpperCase(propertyName.charAt(0));
113+
if (propertyName.length() > 1) {
114+
setterName += propertyName.substring(1);
115+
}
116+
Method setter = findSetterMethod(obj.getClass(), setterName, value);
117+
if (setter != null) {
118+
setter.setAccessible(true);
119+
setter.invoke(obj, value);
120+
} else {
121+
throw new IllegalAccessException("No setter method found for property '" + propertyName + "'");
122+
}
123+
} catch (Exception e) {
124+
throw new IllegalAccessException("Cannot set property '" + propertyName + "' on " + obj.getClass().getName() + ": " + e.getMessage());
125+
}
126+
}
127+
128+
/**
129+
* Finds a field in the class hierarchy.
130+
*/
131+
private static Field findFieldInHierarchy(Class<?> clazz, String fieldName) {
132+
Class<?> current = clazz;
133+
while (current != null && current != Object.class) {
134+
try {
135+
return current.getDeclaredField(fieldName);
136+
} catch (NoSuchFieldException e) {
137+
current = current.getSuperclass();
138+
}
139+
}
140+
return null;
141+
}
142+
143+
/**
144+
* Finds a setter method that can accept the given value.
145+
*/
146+
private static Method findSetterMethod(Class<?> clazz, String setterName, Object value) {
147+
Method[] methods = clazz.getMethods();
148+
for (Method method : methods) {
149+
if (method.getName().equals(setterName) && method.getParameterTypes().length == 1) {
150+
Class<?> paramType = method.getParameterTypes()[0];
151+
if (value == null || paramType.isAssignableFrom(value.getClass()) ||
152+
(paramType.isPrimitive() && isCompatiblePrimitive(paramType, value.getClass()))) {
153+
return method;
154+
}
155+
}
156+
}
157+
return null;
158+
}
159+
160+
/**
161+
* Checks if a value class is compatible with a primitive parameter type.
162+
*/
163+
private static boolean isCompatiblePrimitive(Class<?> primitiveType, Class<?> valueClass) {
164+
if (primitiveType == int.class) return valueClass == Integer.class;
165+
if (primitiveType == long.class) return valueClass == Long.class;
166+
if (primitiveType == double.class) return valueClass == Double.class;
167+
if (primitiveType == float.class) return valueClass == Float.class;
168+
if (primitiveType == boolean.class) return valueClass == Boolean.class;
169+
if (primitiveType == byte.class) return valueClass == Byte.class;
170+
if (primitiveType == short.class) return valueClass == Short.class;
171+
if (primitiveType == char.class) return valueClass == Character.class;
172+
return false;
173+
}
174+
}

nitrite/src/main/java/org/dizitart/no2/repository/RepositoryOperations.java

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -110,12 +110,12 @@ public <T> Document toDocument(T object, boolean update) {
110110
if (objectIdField != null) {
111111
Field idField = objectIdField.getField();
112112

113-
if (idField.getType() == NitriteId.class) {
113+
Class<?> fieldType = InterfacePropertyHolder.getPropertyType(idField);
114+
if (fieldType == NitriteId.class) {
114115
try {
115-
idField.setAccessible(true);
116-
if (idField.get(object) == null) {
116+
if (FieldAccessHelper.get(idField, object) == null) {
117117
NitriteId id = document.getId();
118-
idField.set(object, id);
118+
FieldAccessHelper.set(idField, object, id);
119119
document.put(objectIdField.getIdFieldName(), nitriteMapper.tryConvert(id, Comparable.class));
120120
} else if (!update) {
121121
// if it is an insert, then we should not allow to insert the document with user
@@ -144,9 +144,8 @@ public Filter createUniqueFilter(Object object) {
144144
}
145145

146146
Field idField = objectIdField.getField();
147-
idField.setAccessible(true);
148147
try {
149-
Object value = idField.get(object);
148+
Object value = FieldAccessHelper.get(idField, object);
150149
if (value == null) {
151150
throw new InvalidIdException("Id value cannot be null");
152151
}
@@ -160,8 +159,10 @@ public void removeNitriteId(Document document) {
160159
document.remove(DOC_ID);
161160
if (objectIdField != null) {
162161
Field idField = objectIdField.getField();
163-
if (idField != null && !objectIdField.isEmbedded() && idField.getType() == NitriteId.class) {
164-
document.remove(idField.getName());
162+
Class<?> fieldType = InterfacePropertyHolder.getPropertyType(idField);
163+
String fieldName = InterfacePropertyHolder.getPropertyName(idField);
164+
if (idField != null && !objectIdField.isEmbedded() && fieldType == NitriteId.class) {
165+
document.remove(fieldName);
165166
}
166167
}
167168
}
@@ -171,7 +172,9 @@ public <I> Filter createIdFilter(I id) {
171172
if (id == null) {
172173
throw new InvalidIdException("Id cannot be null");
173174
}
174-
if (!isCompatibleTypes(id.getClass(), objectIdField.getField().getType())) {
175+
Field idField = objectIdField.getField();
176+
Class<?> fieldType = InterfacePropertyHolder.getPropertyType(idField);
177+
if (!isCompatibleTypes(id.getClass(), fieldType)) {
175178
throw new InvalidIdException("A value of invalid type is provided as id");
176179
}
177180

0 commit comments

Comments
 (0)