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

AuthMe / ConfigMe / 6170387244

13 Sep 2023 09:09AM UTC coverage: 99.394% (+0.002%) from 99.392%
6170387244

push

github

ljacqu
ConfigurationDataBuilder: use private fields, add more documentation, make extension more easy

534 of 548 branches covered (0.0%)

7 of 7 new or added lines in 1 file covered. (100.0%)

1476 of 1485 relevant lines covered (99.39%)

4.55 hits per line

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

95.31
/src/main/java/ch/jalu/configme/configurationdata/ConfigurationDataBuilder.java
1
package ch.jalu.configme.configurationdata;
2

3
import ch.jalu.configme.Comment;
4
import ch.jalu.configme.SettingsHolder;
5
import ch.jalu.configme.exception.ConfigMeException;
6
import ch.jalu.configme.properties.Property;
7
import org.jetbrains.annotations.NotNull;
8
import org.jetbrains.annotations.Nullable;
9

10
import java.lang.reflect.Constructor;
11
import java.lang.reflect.Field;
12
import java.lang.reflect.InvocationTargetException;
13
import java.lang.reflect.Modifier;
14
import java.util.ArrayList;
15
import java.util.Arrays;
16
import java.util.Collections;
17
import java.util.List;
18
import java.util.stream.Stream;
19

20
/**
21
 * Utility class responsible for creating {@link ConfigurationData} by retrieving {@link Property} fields
22
 * from {@link SettingsHolder} implementations and gathering all comments.
23
 */
24
public class ConfigurationDataBuilder {
25

26
    private final @NotNull PropertyListBuilder propertyListBuilder;
27
    private final @NotNull CommentsConfiguration commentsConfiguration;
28

29
    /**
30
     * Constructor. Use {@link #createConfiguration(Class[])} or a similar static method to create configuration data.
31
     * Use the constructors of this class only if you are overriding specific behavior.
32
     */
33
    protected ConfigurationDataBuilder() {
34
        this(new PropertyListBuilder(), new CommentsConfiguration());
8✔
35
    }
1✔
36

37
    /**
38
     * Constructor. Use {@link #createConfiguration(Class[])} or a similar static method to create configuration data.
39
     * Use the constructors of this class only if you are overriding specific behavior.
40
     *
41
     * @param propertyListBuilder property list builder to order and validate property paths
42
     * @param commentsConfiguration comments configuration to keep track of all comments
43
     */
44
    public ConfigurationDataBuilder(@NotNull PropertyListBuilder propertyListBuilder,
45
                                    @NotNull CommentsConfiguration commentsConfiguration) {
2✔
46
        this.propertyListBuilder = propertyListBuilder;
3✔
47
        this.commentsConfiguration = commentsConfiguration;
3✔
48
    }
1✔
49

50
    /**
51
     * Collects all properties and comment data from the provided classes.
52
     * Properties are sorted by their group, and each group is sorted by order of encounter.
53
     *
54
     * @param classes the classes to scan for their property data
55
     * @return collected configuration data
56
     */
57
    @SafeVarargs
58
    public static @NotNull ConfigurationData createConfiguration(@NotNull Class<? extends SettingsHolder>... classes) {
59
        return createConfiguration(Arrays.asList(classes));
4✔
60
    }
61

62
    /**
63
     * Collects all properties and comment data from the provided classes.
64
     * Properties are sorted by their group, and each group is sorted by order of encounter.
65
     *
66
     * @param classes the classes to scan for their property data
67
     * @return collected configuration data
68
     */
69
    public static @NotNull ConfigurationData createConfiguration(
70
                                                           @NotNull Iterable<Class<? extends SettingsHolder>> classes) {
71
        ConfigurationDataBuilder builder = new ConfigurationDataBuilder();
4✔
72
        return builder.collectData(classes);
4✔
73
    }
74

75
    /**
76
     * Manually creates configuration data with the given properties, without any comments. Note that the given
77
     * properties must be in an order that is suitable for exporting. For instance, the default YAML file resource
78
     * requires that all properties with the same parent be grouped together (see {@link PropertyListBuilder}).
79
     *
80
     * @param properties the properties that make up the configuration data
81
     * @return configuration data with the given properties
82
     */
83
    public static @NotNull ConfigurationData createConfiguration(@NotNull List<? extends Property<?>> properties) {
84
        return new ConfigurationDataImpl(properties, Collections.emptyMap());
6✔
85
    }
86

87
    /**
88
     * Manually creates configuration data with the given properties and comments. Note that the given
89
     * properties must be in an order that is suitable for exporting. For instance, the default YAML file resource
90
     * requires that all properties with the same parent be grouped together.
91
     *
92
     * @param properties the properties that make up the configuration data
93
     * @param commentsConfiguration the comments to include in the export
94
     * @return configuration data with the given properties
95
     */
96
    public static @NotNull ConfigurationData createConfiguration(@NotNull List<? extends Property<?>> properties,
97
                                                                 @NotNull CommentsConfiguration commentsConfiguration) {
98
        return new ConfigurationDataImpl(properties, commentsConfiguration.getAllComments());
7✔
99
    }
100

101
    /**
102
     * Collects property data and comment info from the given class and creates a configuration data
103
     * instance with it.
104
     *
105
     * @param classes the classes to process
106
     * @return configuration data with the classes' data
107
     */
108
    public @NotNull ConfigurationData collectData(@NotNull Iterable<Class<? extends SettingsHolder>> classes) {
109
        for (Class<? extends SettingsHolder> clazz : classes) {
10✔
110
            collectProperties(clazz);
3✔
111
            collectSectionComments(clazz);
3✔
112
        }
1✔
113
        return new ConfigurationDataImpl(propertyListBuilder.create(), commentsConfiguration.getAllComments());
10✔
114
    }
115

116
    /**
117
     * Registers all property fields of the given class to this instance's property list builder.
118
     *
119
     * @param clazz the class to process
120
     */
121
    protected void collectProperties(@NotNull Class<?> clazz) {
122
        findFieldsToProcess(clazz).forEach(field -> {
6✔
123
            Property<?> property = getPropertyField(field);
4✔
124
            if (property != null) {
2✔
125
                propertyListBuilder.add(property);
4✔
126
                setCommentForPropertyField(field, property.getPath());
5✔
127
            }
128
        });
1✔
129
    }
1✔
130

131
    protected final @NotNull PropertyListBuilder getPropertyListBuilder() {
132
        return propertyListBuilder;
3✔
133
    }
134

135
    protected final @NotNull CommentsConfiguration getCommentsConfiguration() {
136
        return commentsConfiguration;
3✔
137
    }
138

139
    protected void setCommentForPropertyField(@NotNull Field field, @NotNull String path) {
140
        Comment commentAnnotation = field.getAnnotation(Comment.class);
5✔
141
        if (commentAnnotation != null) {
2✔
142
            commentsConfiguration.setComment(path, commentAnnotation.value());
6✔
143
        }
144
    }
1✔
145

146
    /**
147
     * Returns the given field's value if it is a static {@link Property}.
148
     *
149
     * @param field the field's value to return
150
     * @return the property the field defines, or null if not applicable
151
     */
152
    protected @Nullable Property<?> getPropertyField(@NotNull Field field) {
153
        if (Property.class.isAssignableFrom(field.getType()) && Modifier.isStatic(field.getModifiers())) {
9✔
154
            try {
155
                setFieldAccessibleIfNeeded(field);
3✔
156
                return (Property<?>) field.get(null);
5✔
157
            } catch (IllegalAccessException e) {
1✔
158
                throw new ConfigMeException("Could not fetch field '" + field.getName() + "' from class '"
13✔
159
                    + field.getDeclaringClass().getSimpleName() + "'. Is it maybe not public?", e);
9✔
160
            }
161
        }
162
        return null;
2✔
163
    }
164

165
    /**
166
     * Sets the Field object to be accessible if needed; this makes the code support constants in Kotlin without the
167
     * {@code @JvmField} annotation. This method only calls {@link Field#setAccessible} if it is needed to avoid
168
     * potential issues with a security manager. Within Java itself, only {@code public static final} property fields
169
     * are expected.
170
     *
171
     * @param field the field to process
172
     */
173
    protected void setFieldAccessibleIfNeeded(@NotNull Field field) {
174
        try {
175
            if (!Modifier.isPublic(field.getModifiers())) {
4✔
176
                field.setAccessible(true);
3✔
177
            }
178
            // CHECKSTYLE:OFF
179
        } catch (Exception e) {
×
180
            // CHECKSTYLE: ON
181
            throw new ConfigMeException("Failed to modify access for field '" + field.getName() + "' from class '"
×
182
                + field.getDeclaringClass().getSimpleName() + "'", e);
×
183
        }
1✔
184
    }
1✔
185

186
    protected void collectSectionComments(@NotNull Class<? extends SettingsHolder> clazz) {
187
        SettingsHolder settingsHolder = createSettingsHolderInstance(clazz);
4✔
188
        settingsHolder.registerComments(commentsConfiguration);
4✔
189
    }
1✔
190

191
    /**
192
     * Creates an instance of the given settings holder class.
193
     *
194
     * @param clazz the class to instantiate
195
     * @param <T> the class type
196
     * @return instance of the class
197
     */
198
    protected <T extends SettingsHolder> @NotNull T createSettingsHolderInstance(@NotNull Class<T> clazz) {
199
        try {
200
            Constructor<T> constructor = clazz.getDeclaredConstructor();
5✔
201
            constructor.setAccessible(true);
3✔
202
            return constructor.newInstance();
6✔
203
        } catch (NoSuchMethodException e) {
1✔
204
            throw new ConfigMeException("Expected no-args constructor to be available for " + clazz, e);
13✔
205
        } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
1✔
206
            throw new ConfigMeException("Could not create instance of " + clazz, e);
13✔
207
        }
208
    }
209

210
    /**
211
     * Returns all fields of the class which should be considered as potential {@link Property} definitions.
212
     * Considers the class's parents.
213
     *
214
     * @param clazz the class whose fields should be returned
215
     * @return stream of all the fields to process
216
     */
217
    protected @NotNull Stream<Field> findFieldsToProcess(@NotNull Class<?> clazz) {
218
        // In most cases we expect the class not to have any parent, so we check here and "fast track" this case
219
        if (Object.class.equals(clazz.getSuperclass())) {
5✔
220
            return Arrays.stream(clazz.getDeclaredFields());
4✔
221
        }
222

223
        List<Class<?>> classes = new ArrayList<>();
4✔
224
        Class<?> currentClass = clazz;
2✔
225
        while (currentClass != null && !currentClass.equals(Object.class)) {
6✔
226
            classes.add(currentClass);
4✔
227
            currentClass = currentClass.getSuperclass();
4✔
228
        }
229
        Collections.reverse(classes);
2✔
230

231
        return classes.stream()
4✔
232
            .map(Class::getDeclaredFields)
2✔
233
            .flatMap(Arrays::stream);
1✔
234
    }
235
}
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