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

AuthMe / ConfigMe / 11195997843

05 Oct 2024 08:11PM UTC coverage: 99.411%. Remained the same
11195997843

Pull #444

github

ljacqu
Move channel declaration
Pull Request #444: #442 Update Checkstyle version used by CodeClimate

528 of 540 branches covered (97.78%)

1520 of 1529 relevant lines covered (99.41%)

4.58 hits per line

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

97.14
/src/main/java/ch/jalu/configme/beanmapper/propertydescription/BeanDescriptionFactoryImpl.java
1
package ch.jalu.configme.beanmapper.propertydescription;
2

3
import ch.jalu.configme.Comment;
4
import ch.jalu.configme.beanmapper.ConfigMeMapperException;
5
import ch.jalu.configme.beanmapper.ExportName;
6
import ch.jalu.typeresolver.TypeInfo;
7
import org.jetbrains.annotations.NotNull;
8
import org.jetbrains.annotations.Nullable;
9

10
import java.beans.IntrospectionException;
11
import java.beans.Introspector;
12
import java.beans.PropertyDescriptor;
13
import java.lang.reflect.Field;
14
import java.util.ArrayList;
15
import java.util.Arrays;
16
import java.util.Collection;
17
import java.util.Collections;
18
import java.util.Comparator;
19
import java.util.HashMap;
20
import java.util.HashSet;
21
import java.util.List;
22
import java.util.Map;
23
import java.util.Objects;
24
import java.util.Set;
25
import java.util.UUID;
26
import java.util.stream.Collectors;
27

28
/**
29
 * Creates all {@link BeanPropertyDescription} objects for a given class.
30
 * <p>
31
 * This description factory returns property descriptions for all properties on a class
32
 * for which a getter and setter is associated. Inherited properties are considered.
33
 * <p>
34
 * This implementation supports {@link ExportName} and transient properties, declared either
35
 * with the {@code transient} keyword or by adding the {@link java.beans.Transient} annotation.
36
 */
37
public class BeanDescriptionFactoryImpl implements BeanDescriptionFactory {
2✔
38

39
    private final Map<Class<?>, List<BeanPropertyDescription>> classProperties = new HashMap<>();
6✔
40

41
    /**
42
     * Returns all properties of the given bean class for which there exists a getter and setter.
43
     *
44
     * @param clazz the bean property to process
45
     * @return the bean class's properties to handle
46
     */
47
    @Override
48
    public @NotNull Collection<BeanPropertyDescription> getAllProperties(@NotNull Class<?> clazz) {
49
        return classProperties.computeIfAbsent(clazz, this::collectAllProperties);
8✔
50
    }
51

52
    /**
53
     * Collects all properties available on the given class.
54
     *
55
     * @param clazz the class to process
56
     * @return properties of the class
57
     */
58
    protected @NotNull List<BeanPropertyDescription> collectAllProperties(@NotNull Class<?> clazz) {
59
        List<PropertyDescriptor> descriptors = getWritableProperties(clazz);
4✔
60

61
        List<BeanPropertyDescription> properties = descriptors.stream()
4✔
62
            .map(this::convert)
2✔
63
            .filter(Objects::nonNull)
1✔
64
            .collect(Collectors.toList());
4✔
65

66
        validateProperties(clazz, properties);
4✔
67
        return properties;
2✔
68
    }
69

70
    /**
71
     * Converts a {@link PropertyDescriptor} to a {@link BeanPropertyDescription} object.
72
     *
73
     * @param descriptor the descriptor to convert
74
     * @return the converted object, or null if the property should be skipped
75
     */
76
    protected @Nullable BeanPropertyDescription convert(@NotNull PropertyDescriptor descriptor) {
77
        if (Boolean.TRUE.equals(descriptor.getValue("transient"))) {
6✔
78
            return null;
2✔
79
        }
80

81
        Field field = tryGetField(descriptor.getWriteMethod().getDeclaringClass(), descriptor.getName());
8✔
82
        BeanPropertyComments comments = getComments(field);
4✔
83
        return new BeanPropertyDescriptionImpl(
6✔
84
            getPropertyName(descriptor, field),
3✔
85
            createTypeInfo(descriptor),
2✔
86
            descriptor.getReadMethod(),
2✔
87
            descriptor.getWriteMethod(),
3✔
88
            comments);
89
    }
90

91
    /**
92
     * Returns the comments that are defined on the property. Comments are found by looking for an &#64;{@link Comment}
93
     * annotation on a field with the same name as the property.
94
     *
95
     * @param field the field associated with the property (may be null)
96
     * @return comments for the property (never null)
97
     */
98
    protected @NotNull BeanPropertyComments getComments(@Nullable Field field) {
99
        Comment comment = field == null ? null : field.getAnnotation(Comment.class);
9✔
100
        if (comment != null) {
2✔
101
            UUID uniqueId = comment.repeat() ? null : UUID.randomUUID();
7✔
102
            return new BeanPropertyComments(Arrays.asList(comment.value()), uniqueId);
8✔
103
        }
104
        return BeanPropertyComments.EMPTY;
2✔
105
    }
106

107
    /**
108
     * Returns the field with the given name on the provided class, or null if it doesn't exist.
109
     *
110
     * @param clazz the class to search in
111
     * @param name the field name to look for
112
     * @return the field if matched, otherwise null
113
     */
114
    protected @Nullable Field tryGetField(@NotNull Class<?> clazz, @NotNull String name) {
115
        try {
116
            return clazz.getDeclaredField(name);
4✔
117
        } catch (NoSuchFieldException ignore) {
1✔
118
        }
119
        return null;
2✔
120
    }
121

122
    /**
123
     * Validates the class's properties.
124
     *
125
     * @param clazz the class to which the properties belong
126
     * @param properties the properties that will be used on the class
127
     */
128
    protected void validateProperties(@NotNull Class<?> clazz,
129
                                      @NotNull Collection<BeanPropertyDescription> properties) {
130
        Set<String> names = new HashSet<>(properties.size());
6✔
131
        properties.forEach(property -> {
5✔
132
            if (property.getName().isEmpty()) {
4✔
133
                throw new ConfigMeMapperException("Custom name of " + property + " may not be empty");
14✔
134
            }
135
            if (!names.add(property.getName())) {
5✔
136
                throw new ConfigMeMapperException(
10✔
137
                    clazz + " has multiple properties with name '" + property.getName() + "'");
7✔
138
            }
139
        });
1✔
140
    }
1✔
141

142
    /**
143
     * Returns the name which is used in the export files for the given property descriptor.
144
     *
145
     * @param descriptor the descriptor to get the name for
146
     * @param field the field associated with the property (may be null)
147
     * @return the property name
148
     */
149
    protected @NotNull String getPropertyName(@NotNull PropertyDescriptor descriptor, @Nullable Field field) {
150
        if (field != null && field.isAnnotationPresent(ExportName.class)) {
6✔
151
            return field.getAnnotation(ExportName.class).value();
6✔
152
        }
153
        return descriptor.getName();
3✔
154
    }
155

156
    protected @NotNull TypeInfo createTypeInfo(@NotNull PropertyDescriptor descriptor) {
157
        return new TypeInfo(descriptor.getWriteMethod().getGenericParameterTypes()[0]);
9✔
158
    }
159

160
    /**
161
     * Returns all properties of the given class that are writable
162
     * (all bean properties with an associated read and write method).
163
     *
164
     * @param clazz the class to process
165
     * @return all writable properties of the bean class
166
     */
167
    protected @NotNull List<PropertyDescriptor> getWritableProperties(@NotNull Class<?> clazz) {
168
        PropertyDescriptor[] descriptors;
169
        try {
170
            descriptors = Introspector.getBeanInfo(clazz).getPropertyDescriptors();
4✔
171
        } catch (IntrospectionException e) {
×
172
            throw new IllegalStateException(e);
×
173
        }
1✔
174
        List<PropertyDescriptor> writableProperties = new ArrayList<>(descriptors.length);
6✔
175
        for (PropertyDescriptor descriptor : descriptors) {
16✔
176
            if (descriptor.getWriteMethod() != null && descriptor.getReadMethod() != null) {
6✔
177
                writableProperties.add(descriptor);
4✔
178
            }
179
        }
180
        return sortPropertiesList(clazz, writableProperties);
5✔
181
    }
182

183
    /**
184
     * Returns a sorted list of the given properties which will be used for further processing and whose
185
     * order will be maintained. May return the same list.
186
     *
187
     * @param clazz the class from which the properties come from
188
     * @param properties the properties to sort
189
     * @return sorted properties
190
     */
191
    protected @NotNull List<PropertyDescriptor> sortPropertiesList(@NotNull Class<?> clazz,
192
                                                                   @NotNull List<PropertyDescriptor> properties) {
193
        Map<String, Integer> fieldNameByIndex = createFieldNameOrderMap(clazz);
4✔
194
        int maxIndex = fieldNameByIndex.size();
3✔
195

196
        properties.sort(Comparator.comparing(property -> {
6✔
197
            Integer index = fieldNameByIndex.get(property.getName());
6✔
198
            return index == null ? maxIndex : index;
8✔
199
        }));
200
        return properties;
2✔
201
    }
202

203
    /**
204
     * Creates a map of index (encounter number) by field name for all fields of the given class,
205
     * including its superclasses. Fields are sorted by declaration order in the classes; sorted
206
     * by top-most class in the inheritance hierarchy to the lowest (the class provided as parameter).
207
     *
208
     * @param clazz the class to create the field index map for
209
     * @return map with all field names as keys and its index as value
210
     */
211
    protected @NotNull Map<String, Integer> createFieldNameOrderMap(@NotNull Class<?> clazz) {
212
        Map<String, Integer> nameByIndex = new HashMap<>();
4✔
213
        int i = 0;
2✔
214
        for (Class currentClass : collectClassAndAllParents(clazz)) {
12✔
215
            for (Field field : currentClass.getDeclaredFields()) {
17✔
216
                nameByIndex.put(field.getName(), i);
7✔
217
                ++i;
1✔
218
            }
219
        }
1✔
220
        return nameByIndex;
2✔
221
    }
222

223
    /**
224
     * Returns a list of the class's parents, including the given class itself, with the top-most parent
225
     * coming first. Does not include the Object class.
226
     *
227
     * @param clazz the class whose parents should be collected
228
     * @return list with all of the class's parents, sorted by highest class in the hierarchy to lowest
229
     */
230
    protected @NotNull List<Class<?>> collectClassAndAllParents(@NotNull Class<?> clazz) {
231
        List<Class<?>> parents = new ArrayList<>();
4✔
232
        Class<?> curClass = clazz;
2✔
233
        while (curClass != null && curClass != Object.class) {
5✔
234
            parents.add(curClass);
4✔
235
            curClass = curClass.getSuperclass();
4✔
236
        }
237
        Collections.reverse(parents);
2✔
238
        return parents;
2✔
239
    }
240
}
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