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

AuthMe / ConfigMe / 7041999669

30 Nov 2023 04:25AM UTC coverage: 99.413%. Remained the same
7041999669

Pull #401

github

web-flow
Bump actions/setup-java from 3 to 4

Bumps [actions/setup-java](https://github.com/actions/setup-java) from 3 to 4.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #401: Bump actions/setup-java from 3 to 4

527 of 540 branches covered (0.0%)

1524 of 1533 relevant lines covered (99.41%)

4.57 hits per line

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

99.29
/src/main/java/ch/jalu/configme/beanmapper/MapperImpl.java
1
package ch.jalu.configme.beanmapper;
2

3
import ch.jalu.configme.beanmapper.context.ExportContext;
4
import ch.jalu.configme.beanmapper.context.ExportContextImpl;
5
import ch.jalu.configme.beanmapper.context.MappingContext;
6
import ch.jalu.configme.beanmapper.context.MappingContextImpl;
7
import ch.jalu.configme.beanmapper.leafvaluehandler.LeafValueHandler;
8
import ch.jalu.configme.beanmapper.leafvaluehandler.LeafValueHandlerImpl;
9
import ch.jalu.configme.beanmapper.leafvaluehandler.MapperLeafType;
10
import ch.jalu.configme.beanmapper.propertydescription.BeanDescriptionFactory;
11
import ch.jalu.configme.beanmapper.propertydescription.BeanDescriptionFactoryImpl;
12
import ch.jalu.configme.beanmapper.propertydescription.BeanPropertyComments;
13
import ch.jalu.configme.beanmapper.propertydescription.BeanPropertyDescription;
14
import ch.jalu.configme.properties.convertresult.ConvertErrorRecorder;
15
import ch.jalu.configme.properties.convertresult.ValueWithComments;
16
import ch.jalu.typeresolver.TypeInfo;
17
import org.jetbrains.annotations.NotNull;
18
import org.jetbrains.annotations.Nullable;
19

20
import java.util.ArrayList;
21
import java.util.Collection;
22
import java.util.LinkedHashMap;
23
import java.util.LinkedHashSet;
24
import java.util.List;
25
import java.util.Map;
26
import java.util.Optional;
27
import java.util.TreeMap;
28

29
import static ch.jalu.configme.internal.PathUtils.OPTIONAL_SPECIFIER;
30
import static ch.jalu.configme.internal.PathUtils.pathSpecifierForIndex;
31
import static ch.jalu.configme.internal.PathUtils.pathSpecifierForMapKey;
32

33
/**
34
 * Implementation of {@link Mapper}.
35
 * <p>
36
 * Maps a section of a property resource to the provided JavaBean class. The mapping is based on the bean's properties,
37
 * whose names must correspond with the names in the property resource. For example, if a JavaBean class has a property
38
 * {@code length} and should be mapped from the property resource's value at path {@code definition}, the mapper will
39
 * look up {@code definition.length} to get the value of the JavaBean property.
40
 * <p>
41
 * Classes must be JavaBeans. These are simple classes with private fields, accompanied by getters and setters.
42
 * <b>The mapper only considers properties which have both a getter and a setter method.</b> Any Java class without
43
 * at least one property with both a getter <i>and</i> a setter is not considered as a JavaBean class. Such classes can
44
 * be supported by implementing a custom {@link MapperLeafType} that performs the conversion from the value coming
45
 * from the property reader to an object of the class's type.
46
 * <p>
47
 * <b>Recursion:</b> the mapping of values to a JavaBean is performed recursively, i.e. a JavaBean may have other
48
 * JavaBeans as fields and generic types at any arbitrary "depth".
49
 * <p>
50
 * <b>Collections</b> are only supported if they are explicitly typed, i.e. a field of {@code List<String>}
51
 * is supported but {@code List<?>} and {@code List<T extends Number>} are not supported. Specifically, you may
52
 * only declare fields of type {@link java.util.List} or {@link java.util.Set}, or a parent type ({@link Collection}
53
 * or {@link Iterable}).
54
 * Fields of type <b>Map</b> are supported also, with similar limitations. Additionally, maps may only have
55
 * {@code String} as key type, but no restrictions are imposed on the value type.
56
 * <p>
57
 * JavaBeans may have <b>optional fields</b>. If the mapper cannot map the property resource value to the corresponding
58
 * field, it only treats it as a failure if the field's value is {@code null}. If the field has a default value assigned
59
 * to it on initialization, the default value remains and the mapping process continues. A JavaBean field whose value is
60
 * {@code null} signifies a failure and stops the mapping process immediately.
61
 */
62
public class MapperImpl implements Mapper {
63

64
    /** Marker object to signal that null is meant to be used as value. */
65
    public static final Object RETURN_NULL = new Object();
5✔
66

67
    // ---------
68
    // Fields and general configurable methods
69
    // ---------
70

71
    private final BeanDescriptionFactory beanDescriptionFactory;
72
    private final LeafValueHandler leafValueHandler;
73

74
    public MapperImpl() {
75
        this(new BeanDescriptionFactoryImpl(),
7✔
76
             new LeafValueHandlerImpl(LeafValueHandlerImpl.createDefaultLeafTypes()));
2✔
77
    }
1✔
78

79
    public MapperImpl(@NotNull BeanDescriptionFactory beanDescriptionFactory,
80
                      @NotNull LeafValueHandler leafValueHandler) {
2✔
81
        this.beanDescriptionFactory = beanDescriptionFactory;
3✔
82
        this.leafValueHandler = leafValueHandler;
3✔
83
    }
1✔
84

85
    protected final @NotNull BeanDescriptionFactory getBeanDescriptionFactory() {
86
        return beanDescriptionFactory;
3✔
87
    }
88

89
    protected final @NotNull LeafValueHandler getLeafValueHandler() {
90
        return leafValueHandler;
3✔
91
    }
92

93
    protected @NotNull MappingContext createRootMappingContext(@NotNull TypeInfo beanType,
94
                                                               @NotNull ConvertErrorRecorder errorRecorder) {
95
        return MappingContextImpl.createRoot(beanType, errorRecorder);
4✔
96
    }
97

98
    protected @NotNull ExportContext createRootExportContext() {
99
        return ExportContextImpl.createRoot();
2✔
100
    }
101

102

103
    // ---------
104
    // Export
105
    // ---------
106

107
    @Override
108
    public @Nullable Object toExportValue(@NotNull Object value) {
109
        return toExportValue(value, createRootExportContext());
6✔
110
    }
111

112
    /**
113
     * Transforms the given value to an object suitable for the export to a configuration file.
114
     *
115
     * @param value the value to transform
116
     * @param exportContext export context
117
     * @return export value to use
118
     */
119
    protected @Nullable Object toExportValue(@Nullable Object value, @NotNull ExportContext exportContext) {
120
        // Step 1: attempt simple value transformation
121
        Object exportValue = leafValueHandler.toExportValue(value, exportContext);
6✔
122
        if (exportValue != null || value == null) {
4✔
123
            return unwrapReturnNull(exportValue);
3✔
124
        }
125

126
        // Step 2: handle special cases like Collection
127
        exportValue = createExportValueForSpecialTypes(value, exportContext);
5✔
128
        if (exportValue != null) {
2✔
129
            return unwrapReturnNull(exportValue);
3✔
130
        }
131

132
        // Step 3: treat as bean
133
        Map<String, Object> mappedBean = new LinkedHashMap<>();
4✔
134
        for (BeanPropertyDescription property : beanDescriptionFactory.getAllProperties(value.getClass())) {
14✔
135
            Object exportValueOfProperty = toExportValue(property.getValue(value), exportContext);
7✔
136
            if (exportValueOfProperty != null) {
2✔
137
                BeanPropertyComments propComments = property.getComments();
3✔
138
                if (exportContext.shouldInclude(propComments)) {
4✔
139
                    exportContext.registerComment(propComments);
3✔
140
                    exportValueOfProperty = new ValueWithComments(exportValueOfProperty,
4✔
141
                        propComments.getComments(), propComments.getUuid());
5✔
142
                }
143
                mappedBean.put(property.getName(), exportValueOfProperty);
6✔
144
            }
145
        }
1✔
146
        return mappedBean;
2✔
147
    }
148

149
    /**
150
     * Handles values of types which need special handling (such as Optional). Null means the value is not
151
     * a special type and that the export value should be built differently. Use {@link #RETURN_NULL} to
152
     * signal that null should be used as the export value of the provided value.
153
     *
154
     * @param value the value to convert
155
     * @param exportContext export context
156
     * @return the export value to use or {@link #RETURN_NULL}, or null if not applicable
157
     */
158
    protected @Nullable Object createExportValueForSpecialTypes(@Nullable Object value,
159
                                                                @NotNull ExportContext exportContext) {
160
        if (value instanceof Iterable<?>) {
3✔
161
            int index = 0;
2✔
162
            List<Object> result = new ArrayList<>();
4✔
163
            for (Object entry : (Iterable<?>) value) {
10✔
164
                ExportContext entryContext = exportContext.createChildContext(pathSpecifierForIndex(index));
5✔
165
                result.add(toExportValue(entry, entryContext));
7✔
166
                ++index;
1✔
167
            }
1✔
168
            return result;
2✔
169
        }
170

171
        if (value instanceof Map<?, ?>) {
3✔
172
            Map<Object, Object> result = new LinkedHashMap<>();
4✔
173
            for (Map.Entry<?, ?> entry : ((Map<?, ?>) value).entrySet()) {
12✔
174
                ExportContext entryContext = exportContext.createChildContext(pathSpecifierForMapKey(entry));
5✔
175
                result.put(entry.getKey(), toExportValue(entry.getValue(), entryContext));
10✔
176
            }
1✔
177
            return result;
2✔
178
        }
179

180
        if (value instanceof Optional<?>) {
3✔
181
            Optional<?> optional = (Optional<?>) value;
3✔
182
            return optional
5✔
183
                .map(v -> toExportValue(v, exportContext.createChildContext(OPTIONAL_SPECIFIER)))
9✔
184
                .orElse(RETURN_NULL);
1✔
185
        }
186

187
        return null;
2✔
188
    }
189

190
    protected static @Nullable Object unwrapReturnNull(@Nullable Object o) {
191
        return o == RETURN_NULL ? null : o;
7✔
192
    }
193

194
    // ---------
195
    // Bean mapping
196
    // ---------
197

198
    @Override
199
    public @Nullable Object convertToBean(@Nullable Object value, @NotNull TypeInfo targetType,
200
                                          @NotNull ConvertErrorRecorder errorRecorder) {
201
        if (value == null) {
2✔
202
            return null;
2✔
203
        }
204

205
        return convertValueForType(createRootMappingContext(targetType, errorRecorder), value);
8✔
206
    }
207

208
    /**
209
     * Main method for converting a value to another type.
210
     *
211
     * @param context the mapping context
212
     * @param value the value to convert from
213
     * @return object whose type matches the one in the mapping context, or null if not applicable
214
     */
215
    protected @Nullable Object convertValueForType(@NotNull MappingContext context, @Nullable Object value) {
216
        // Step 1: check if the value is a leaf
217
        Object result = leafValueHandler.convert(value, context);
6✔
218
        if (result != null) {
2✔
219
            return result;
2✔
220
        }
221

222
        // Step 2: check if we have a special type like List that is handled separately
223
        result = convertSpecialTypes(context, value);
5✔
224
        if (result != null) {
2✔
225
            return result;
2✔
226
        }
227

228
        // Step 3: last possibility - assume it's a bean and try to map values to its structure
229
        return createBean(context, value);
5✔
230
    }
231

232
    /**
233
     * Converts types in the bean mapping process which require special handling.
234
     *
235
     * @param context the mapping context
236
     * @param value the value to convert from
237
     * @return object whose type matches the one in the mapping context, or null if not applicable
238
     */
239
    protected @Nullable Object convertSpecialTypes(@NotNull MappingContext context, @Nullable Object value) {
240
        final Class<?> rawClass = context.getTargetTypeAsClassOrThrow();
3✔
241
        if (Iterable.class.isAssignableFrom(rawClass)) {
4✔
242
            return convertToCollection(context, value);
5✔
243
        } else if (Map.class.isAssignableFrom(rawClass)) {
4✔
244
            return convertToMap(context, value);
5✔
245
        } else if (Optional.class.isAssignableFrom(rawClass)) {
4✔
246
            return convertOptional(context, value);
5✔
247
        }
248
        return null;
2✔
249
    }
250

251
    // -- Collection
252

253
    /**
254
     * Handles the creation of Collection properties.
255
     *
256
     * @param context the mapping context
257
     * @param value the value to map from
258
     * @return Collection property from the value, or null if not applicable
259
     */
260
    @SuppressWarnings({"unchecked", "rawtypes"})
261
    protected @Nullable Collection<?> convertToCollection(@NotNull MappingContext context, @Nullable Object value) {
262
        if (value instanceof Iterable<?>) {
3✔
263
            TypeInfo entryType = context.getTargetTypeArgumentOrThrow(0);
4✔
264
            Collection result = createCollectionMatchingType(context);
4✔
265

266
            int index = 0;
2✔
267
            for (Object entry : (Iterable<?>) value) {
10✔
268
                MappingContext entryContext = context.createChild(pathSpecifierForIndex(index), entryType);
6✔
269
                Object convertedEntry = convertValueForType(entryContext, entry);
5✔
270
                if (convertedEntry == null) {
2✔
271
                    context.registerError("Cannot convert value at index " + index);
11✔
272
                } else {
273
                    result.add(convertedEntry);
4✔
274
                }
275
                ++index;
1✔
276
            }
1✔
277
            return result;
2✔
278
        }
279
        return null;
2✔
280
    }
281

282
    /**
283
     * Creates a Collection of a type which can be assigned to the provided type.
284
     *
285
     * @param mappingContext the current mapping context with a collection type
286
     * @return Collection of matching type
287
     */
288
    protected @NotNull Collection<?> createCollectionMatchingType(@NotNull MappingContext mappingContext) {
289
        Class<?> collectionType = mappingContext.getTargetTypeAsClassOrThrow();
3✔
290
        if (collectionType.isAssignableFrom(ArrayList.class)) {
4✔
291
            return new ArrayList<>();
4✔
292
        } else if (collectionType.isAssignableFrom(LinkedHashSet.class)) {
4✔
293
            return new LinkedHashSet<>();
4✔
294
        } else {
295
            throw new ConfigMeMapperException(mappingContext, "Unsupported collection type '" + collectionType + "'");
15✔
296
        }
297
    }
298

299
    // -- Map
300

301
    /**
302
     * Handles the creation of a Map property.
303
     *
304
     * @param context mapping context
305
     * @param value value to map from
306
     * @return Map property, or null if not applicable
307
     */
308
    @SuppressWarnings({"unchecked", "rawtypes"})
309
    protected @Nullable Map<?, ?> convertToMap(@NotNull MappingContext context, @Nullable Object value) {
310
        if (value instanceof Map<?, ?>) {
3✔
311
            if (context.getTargetTypeArgumentOrThrow(0).toClass() != String.class) {
6✔
312
                throw new ConfigMeMapperException(context, "The key type of maps may only be of String type");
6✔
313
            }
314
            TypeInfo mapValueType = context.getTargetTypeArgumentOrThrow(1);
4✔
315

316
            Map<String, ?> entries = (Map<String, ?>) value;
3✔
317
            Map result = createMapMatchingType(context);
4✔
318
            for (Map.Entry<String, ?> entry : entries.entrySet()) {
11✔
319
                MappingContext entryContext = context.createChild(pathSpecifierForMapKey(entry), mapValueType);
6✔
320
                Object mappedValue = convertValueForType(entryContext, entry.getValue());
6✔
321
                if (mappedValue == null) {
2✔
322
                    context.registerError("Cannot map value for key " + entry.getKey());
13✔
323
                } else {
324
                    result.put(entry.getKey(), mappedValue);
6✔
325
                }
326
            }
1✔
327
            return result;
2✔
328
        }
329
        return null;
2✔
330
    }
331

332
    /**
333
     * Creates a Map of a type which can be assigned to the provided type.
334
     *
335
     * @param mappingContext the current mapping context with a map type
336
     * @return Map of matching type
337
     */
338
    protected @NotNull Map<?, ?> createMapMatchingType(@NotNull MappingContext mappingContext) {
339
        Class<?> mapType = mappingContext.getTargetTypeAsClassOrThrow();
3✔
340
        if (mapType.isAssignableFrom(LinkedHashMap.class)) {
4✔
341
            return new LinkedHashMap<>();
4✔
342
        } else if (mapType.isAssignableFrom(TreeMap.class)) {
4✔
343
            return new TreeMap<>();
4✔
344
        } else {
345
            throw new ConfigMeMapperException(mappingContext, "Unsupported map type '" + mapType + "'");
15✔
346
        }
347
    }
348

349
    // -- Optional
350

351
    // Return value is never null, but if someone wants to override this, it's fine for it to be null
352
    protected @Nullable Object convertOptional(@NotNull MappingContext context, @Nullable Object value) {
353
        MappingContext childContext = context.createChild(OPTIONAL_SPECIFIER, context.getTargetTypeArgumentOrThrow(0));
7✔
354
        Object result = convertValueForType(childContext, value);
5✔
355
        return Optional.ofNullable(result);
3✔
356
    }
357

358
    // -- Bean
359

360
    /**
361
     * Converts the provided value to the requested JavaBeans class if possible.
362
     *
363
     * @param context mapping context (incl. desired type)
364
     * @param value the value from the property resource
365
     * @return the converted value, or null if not possible
366
     */
367
    protected @Nullable Object createBean(@NotNull MappingContext context, @Nullable Object value) {
368
        // Ensure that the value is a map so we can map it to a bean
369
        if (!(value instanceof Map<?, ?>)) {
3✔
370
            return null;
2✔
371
        }
372

373
        Collection<BeanPropertyDescription> properties =
3✔
374
            beanDescriptionFactory.getAllProperties(context.getTargetTypeAsClassOrThrow());
3✔
375
        // Check that we have properties (or else we don't have a bean)
376
        if (properties.isEmpty()) {
3!
377
            return null;
×
378
        }
379

380
        Map<?, ?> entries = (Map<?, ?>) value;
3✔
381
        Object bean = createBeanMatchingType(context);
4✔
382
        for (BeanPropertyDescription property : properties) {
10✔
383
            Object result = convertValueForType(
5✔
384
                context.createChild(property.getName(), property.getTypeInformation()),
6✔
385
                entries.get(property.getName()));
2✔
386
            if (result == null) {
2✔
387
                if (property.getValue(bean) == null) {
4✔
388
                    return null; // We do not support beans with a null value
2✔
389
                }
390
                context.registerError("No value found, fallback to field default value");
4✔
391
            } else {
392
                property.setValue(bean, result);
4✔
393
            }
394
        }
1✔
395
        return bean;
2✔
396
    }
397

398
    /**
399
     * Creates an object matching the given type information.
400
     *
401
     * @param mappingContext current mapping context
402
     * @return new instance of the given type
403
     */
404
    protected @NotNull Object createBeanMatchingType(@NotNull MappingContext mappingContext) {
405
        // clazz is never null given the only path that leads to this method already performs that check
406
        final Class<?> clazz = mappingContext.getTargetTypeAsClassOrThrow();
3✔
407
        try {
408
            return clazz.getDeclaredConstructor().newInstance();
8✔
409
        } catch (ReflectiveOperationException e) {
1✔
410
            throw new ConfigMeMapperException(mappingContext, "Could not create object of type '"
9✔
411
                + clazz.getName() + "'. It is required to have a default constructor", e);
8✔
412
        }
413
    }
414
}
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