• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

burningwave / reflection / #75

18 Oct 2023 02:10PM UTC coverage: 74.528%. Remained the same
#75

push

Roberto-Gentili
Releasing new version

790 of 1060 relevant lines covered (74.53%)

0.75 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.03
/src/main/java/org/burningwave/reflection/FieldAccessor.java
1
/*
2
 * This file is part of Burningwave Reflection.
3
 *
4
 * Author: Roberto Gentili
5
 *
6
 * Hosted at: https://github.com/burningwave/reflection
7
 *
8
 * --
9
 *
10
 * The MIT License (MIT)
11
 *
12
 * Copyright (c) 2022-2023 Roberto Gentili
13
 *
14
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
15
 * documentation files (the "Software"), to deal in the Software without restriction, including without
16
 * limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
17
 * the Software, and to permit persons to whom the Software is furnished to do so, subject to the following
18
 * conditions:
19
 *
20
 * The above copyright notice and this permission notice shall be included in all copies or substantial
21
 * portions of the Software.
22
 *
23
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
24
 * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
25
 * EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
26
 * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
27
 * OR OTHER DEALINGS IN THE SOFTWARE.
28
 */
29
package org.burningwave.reflection;
30

31

32
import java.lang.reflect.Array;
33
import java.lang.reflect.Field;
34
import java.util.ArrayList;
35
import java.util.Collection;
36
import java.util.Iterator;
37
import java.util.List;
38
import java.util.Map;
39
import java.util.function.Supplier;
40
import java.util.regex.Matcher;
41
import java.util.regex.Pattern;
42

43
import org.burningwave.Throwables;
44
import org.burningwave.ThrowingFunction;
45

46
import io.github.toolfactory.jvm.function.template.ThrowingBiFunction;
47

48
@SuppressWarnings("unchecked")
49
public abstract class FieldAccessor {
50

51
        public final static FieldAccessor INSTANCE;
52

53
        private final static String REG_EXP_FOR_INDEXES_OF_INDEXED_FIELDS = "\\[(.*?)\\]";
54
        private final static String REG_EXP_FOR_SIMPLE_FIELDS = "([a-zA-Z\\$\\_\\-0-9]*)(\\[*.*)";
55

56
        static {
57
                INSTANCE = new ByFieldOrByMethod();
1✔
58
        }
1✔
59
        private List<ThrowingBiFunction<Object, String, Object, Throwable>> fieldRetrievers;
60
        private List<ThrowingFunction<Object[], Boolean, Throwable>> fieldSetters;
61
        private Pattern indexesSearcherForIndexedField;
62

63
        private Pattern simpleFieldSearcher;
64

65
        FieldAccessor() {
1✔
66
                this.fieldRetrievers = getFieldRetrievers();
1✔
67
                this.fieldSetters= getFieldSetters();
1✔
68
                this.simpleFieldSearcher = Pattern.compile(REG_EXP_FOR_SIMPLE_FIELDS);
1✔
69
                this.indexesSearcherForIndexedField = Pattern.compile(REG_EXP_FOR_INDEXES_OF_INDEXED_FIELDS);
1✔
70
        }
1✔
71

72
        public <T> T get(Object obj, String path) {
73
                if (path == null) {
1✔
74
                        throw new IllegalArgumentException("Field path cannot be null");
×
75
                }
76
                String[] pathSegments = path.split("\\.");
1✔
77
                Object objToReturn = obj;
1✔
78
                for (int j = 0; j < pathSegments.length; j++) {
1✔
79
                        objToReturn = getField(j != 0 ? objToReturn : obj, pathSegments[j]);
1✔
80
                }
81
                return (T)objToReturn;
1✔
82
        }
83

84
        public void set(Object obj, String path, Object value) {
85
                if (path == null) {
1✔
86
                        throw new IllegalArgumentException("Field path cannot be null");
×
87
                }
88
                if (path.trim().isEmpty()) {
1✔
89
                        return;
×
90
                }
91
                Object target =
1✔
92
                                path.contains(".")?
1✔
93
                                                get(obj, path.substring(0, path.lastIndexOf("."))) :
1✔
94
                                                        obj;
95
                String targetPathSegment =
1✔
96
                                path.contains(".")?
1✔
97
                                                path.substring(path.lastIndexOf(".") + 1, path.length()) :
1✔
98
                                                        path;
99
                setField(target, targetPathSegment, value);
1✔
100
        }
1✔
101

102
        abstract List<ThrowingBiFunction<Object, String, Object, Throwable>> getFieldRetrievers();
103

104
        abstract List<ThrowingFunction<Object[], Boolean, Throwable>> getFieldSetters();
105

106
        Object retrieveFieldByDirectAccess(Object target, String pathSegment) throws IllegalAccessException {
107
                if (pathSegment.trim().isEmpty()) {
1✔
108
                        return target;
1✔
109
                }
110
                return Fields.INSTANCE.get(target, pathSegment);
1✔
111
        }
112

113
        Boolean setFieldByDirectAccess(Object target, String pathSegment, Object value) throws IllegalAccessException {
114
                Matcher matcher = simpleFieldSearcher.matcher(pathSegment);
1✔
115
                matcher.find();
1✔
116
                if (matcher.group(2).isEmpty()) {
1✔
117
                        Field field = Fields.INSTANCE.findOneAndMakeItAccessible(target.getClass(), matcher.group(1));
1✔
118
                        Fields.INSTANCE.set(target, field, value);
1✔
119
                } else {
1✔
120
                        if (target.getClass().isArray() || target instanceof Map || target instanceof Collection) {
1✔
121
                                setInIndexedField(target, matcher.group(2), value);
1✔
122
                        } else {
123
                                Field field = Fields.INSTANCE.findOneAndMakeItAccessible(target.getClass(), matcher.group(1));
1✔
124
                                setInIndexedField(field.get(target), matcher.group(2), value);
1✔
125
                        }
126
                }
127
                return Boolean.TRUE;
1✔
128
        }
129

130

131
        private <T> int convertAndCheckIndex(Collection<T> collection, String indexAsString) {
132
                int index = Integer.valueOf(indexAsString);
1✔
133
                if (collection.size() < index) {
1✔
134
                        throw new IndexOutOfBoundsException(("Illegal index "+ indexAsString +", collection size " + collection.size()));
×
135
                }
136
                return index;
1✔
137
        }
138

139
        private Object getField(Object obj, String pathSegment) {
140
                Object objToReturn = null;
1✔
141
                Matcher matcher = simpleFieldSearcher.matcher(pathSegment);
1✔
142
                matcher.find();
1✔
143
                List<Throwable> exceptions = new ArrayList<>();
1✔
144
                for (ThrowingBiFunction<Object, String, Object, Throwable> retriever : fieldRetrievers) {
1✔
145
                        try {
146
                                if ((objToReturn = retriever.apply(obj, matcher.group(1))) != null) {
1✔
147
                                        break;
1✔
148
                                }
149
                        } catch (Throwable exc) {
×
150
                                exceptions.add(exc);
×
151
                        }
×
152
                }
×
153
                manageGetFieldExceptions(exceptions);
1✔
154
                if (!matcher.group(2).isEmpty()) {
1✔
155
                        try {
156
                                objToReturn = retrieveFromIndexedField(objToReturn, matcher.group(2));
1✔
157
                        } catch (Throwable exc) {
×
158
                                exceptions.add(exc);
×
159
                        }
1✔
160
                }
161
                return objToReturn;
1✔
162
        }
163

164
        private void manageGetFieldExceptions(List<Throwable> exceptions) {
165
                if (exceptions.size() == fieldRetrievers.size()) {
1✔
166
                        Throwables.INSTANCE.throwException(exceptions.iterator().next());
×
167
                }
168
        }
1✔
169

170
        private <T> Object retrieveFromIndexedField(Object fieldValue, String indexes) {
171
                Matcher matcher = indexesSearcherForIndexedField.matcher(indexes);
1✔
172
                if (matcher.find()) {
1✔
173
                        String index = matcher.group(1);
1✔
174
                        Supplier<Object> propertyRetriever = null;
1✔
175
                        if (fieldValue.getClass().isArray()) {
1✔
176
                                propertyRetriever = () -> Array.get(fieldValue, Integer.valueOf(index));
1✔
177
                        } else if (fieldValue instanceof List) {
1✔
178
                                propertyRetriever = () -> ((List<?>)fieldValue).get(Integer.valueOf(index));
1✔
179
                        } else if (fieldValue instanceof Map) {
1✔
180
                                propertyRetriever = () -> ((Map<?, ?>)fieldValue).get(index);
1✔
181
                        } else if (fieldValue instanceof Collection) {
1✔
182
                                propertyRetriever = () -> {
1✔
183
                                        Collection<T> collection = (Collection<T>)fieldValue;
1✔
184
                                        int indexAsInt = convertAndCheckIndex(collection, index);
1✔
185
                                        Iterator<T> itr = collection.iterator();
1✔
186
                                        int currentIterationIndex = 0;
1✔
187
                                        while (itr.hasNext()) {
1✔
188
                                                Object currentIteartedObject = itr.next();
1✔
189
                                                if (currentIterationIndex++ == indexAsInt) {
1✔
190
                                                        return currentIteartedObject;
1✔
191
                                                }
192
                                        }
1✔
193
                                        return null;
×
194
                                };
195
                        } else {
196
                                return Throwables.INSTANCE.throwException("indexed property {} of type {} is not supporterd", fieldValue, fieldValue.getClass());
×
197
                        }
198
                        return retrieveFromIndexedField(
1✔
199
                                propertyRetriever.get(),
1✔
200
                                indexes.substring(matcher.end(), indexes.length())
1✔
201
                        );
202
                }
203
                return fieldValue;
1✔
204
        }
205

206
        private void setField(Object target, String pathSegment, Object value) {
207
                List<Throwable> exceptions = new ArrayList<>();
1✔
208
                for (ThrowingFunction<Object[], Boolean, Throwable> setter : fieldSetters) {
1✔
209
                        try {
210
                                setter.apply(new Object[] {target, pathSegment, value});
1✔
211
                                break;
1✔
212
                        } catch (Throwable exc) {
×
213
                                exceptions.add(exc);
×
214
                        }
215
                }
×
216
                manageGetFieldExceptions(exceptions);
1✔
217
        }
1✔
218

219
        private <T> void setIndexedValue(Collection<T> collection, String index, Object value) {
220
                int indexAsInt = convertAndCheckIndex(collection, index);
1✔
221
                List<T> tempList = new ArrayList<>();
1✔
222
                Iterator<T> itr = collection.iterator();
1✔
223
                while (itr.hasNext()) {
1✔
224
                        tempList.add(itr.next());
1✔
225
                }
226
                int iterationIndex = 0;
1✔
227
                collection.clear();
1✔
228
                itr = tempList.iterator();
1✔
229
                while (itr.hasNext()) {
1✔
230
                        T origVal = itr.next();
1✔
231
                        if (iterationIndex++ != indexAsInt) {
1✔
232
                                collection.add(origVal);
1✔
233
                        } else {
234
                                collection.add((T)value);
1✔
235
                        }
236
                }
1✔
237
        }
1✔
238

239
        private <T> void setInIndexedField(Object fieldValue, String indexes, Object value) {
240
                Matcher matcher = indexesSearcherForIndexedField.matcher(indexes);
1✔
241
                int lastIndexOf = 0;
1✔
242
                String index = null;
1✔
243
                while (matcher.find()) {
1✔
244
                        index = matcher.group(1);
1✔
245
                        lastIndexOf = matcher.start();
1✔
246
                }
247
                Object targetObject = retrieveFromIndexedField(fieldValue, indexes.substring(0, lastIndexOf));
1✔
248
                if (targetObject.getClass().isArray()) {
1✔
249
                        Array.set(targetObject, Integer.valueOf(index), value);
1✔
250
                } else if (targetObject instanceof List) {
1✔
251
                        ((List<T>)targetObject).set(Integer.valueOf(index), (T)value);
1✔
252
                } else if (targetObject instanceof Map) {
1✔
253
                        ((Map<String, T>)targetObject).put(index, (T)value);
1✔
254
                } else if (targetObject instanceof Collection) {
1✔
255
                        setIndexedValue((Collection<T>) targetObject, index, value);
1✔
256
                } else {
257
                        Throwables.INSTANCE.throwException("indexed property {} of type {} is not supporterd", fieldValue, fieldValue.getClass());
×
258
                }
259
        }
1✔
260

261
        private static class ByFieldOrByMethod extends FieldAccessor {
262

263
                private ByFieldOrByMethod() {
264
                        super();
1✔
265
                }
1✔
266

267
                @Override
268
                List<ThrowingBiFunction<Object, String, Object, Throwable>> getFieldRetrievers() {
269
                        List<ThrowingBiFunction<Object, String, Object, Throwable>> retrievers = new ArrayList<>();
1✔
270
                        retrievers.add((object, pathSegment) -> retrieveFieldByDirectAccess(object, pathSegment));
1✔
271
                        return retrievers;
1✔
272
                }
273

274
                @Override
275
                List<ThrowingFunction<Object[], Boolean, Throwable>> getFieldSetters() {
276
                        List<ThrowingFunction<Object[], Boolean, Throwable>> setters  = new ArrayList<>();
1✔
277
                        setters.add(objects -> setFieldByDirectAccess(objects[0], (String)objects[1], objects[2]));
1✔
278
                        return setters;
1✔
279
                }
280
        }
281

282
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc