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

AuthMe / ConfigMe / 6161407230

12 Sep 2023 03:11PM UTC coverage: 98.153% (-0.4%) from 98.541%
6161407230

push

github

ljacqu
Add missing javadoc + constructors, nullability annotations, some tests...

534 of 548 branches covered (0.0%)

16 of 16 new or added lines in 6 files covered. (100.0%)

1488 of 1516 relevant lines covered (98.15%)

4.49 hits per line

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

95.0
/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
    @SuppressWarnings("checkstyle:VisibilityModifier")
5✔
27
    protected @NotNull PropertyListBuilder propertyListBuilder = new PropertyListBuilder();
28
    @SuppressWarnings("checkstyle:VisibilityModifier")
5✔
29
    protected @NotNull CommentsConfiguration commentsConfiguration = new CommentsConfiguration();
30

31
    protected ConfigurationDataBuilder() {
2✔
32
    }
1✔
33

34
    /**
35
     * Collects all properties and comment data from the provided classes.
36
     * Properties are sorted by their group, and each group is sorted by order of encounter.
37
     *
38
     * @param classes the classes to scan for their property data
39
     * @return collected configuration data
40
     */
41
    @SafeVarargs
42
    public static @NotNull ConfigurationData createConfiguration(@NotNull Class<? extends SettingsHolder>... classes) {
43
        return createConfiguration(Arrays.asList(classes));
4✔
44
    }
45

46
    /**
47
     * Collects all properties and comment data from the provided classes.
48
     * Properties are sorted by their group, and each group is sorted by order of encounter.
49
     *
50
     * @param classes the classes to scan for their property data
51
     * @return collected configuration data
52
     */
53
    public static @NotNull ConfigurationData createConfiguration(
54
                                                           @NotNull Iterable<Class<? extends SettingsHolder>> classes) {
55
        ConfigurationDataBuilder builder = new ConfigurationDataBuilder();
4✔
56
        return builder.collectData(classes);
4✔
57
    }
58

59
    public static @NotNull ConfigurationData createConfiguration(@NotNull List<? extends Property<?>> properties) {
60
        return new ConfigurationDataImpl(properties, Collections.emptyMap());
6✔
61
    }
62

63
    public static @NotNull ConfigurationData createConfiguration(@NotNull List<? extends Property<?>> properties,
64
                                                                 @NotNull CommentsConfiguration commentsConfiguration) {
65
        return new ConfigurationDataImpl(properties, commentsConfiguration.getAllComments());
7✔
66
    }
67

68
    /**
69
     * Collects property data and comment info from the given class and creates a configuration data
70
     * instance with it.
71
     *
72
     * @param classes the classes to process
73
     * @return configuration data with the classes' data
74
     */
75
    protected @NotNull ConfigurationData collectData(@NotNull Iterable<Class<? extends SettingsHolder>> classes) {
76
        for (Class<? extends SettingsHolder> clazz : classes) {
10✔
77
            collectProperties(clazz);
3✔
78
            collectSectionComments(clazz);
3✔
79
        }
1✔
80
        return new ConfigurationDataImpl(propertyListBuilder.create(), commentsConfiguration.getAllComments());
10✔
81
    }
82

83
    /**
84
     * Registers all property fields of the given class to this instance's property list builder.
85
     *
86
     * @param clazz the class to process
87
     */
88
    protected void collectProperties(@NotNull Class<?> clazz) {
89
        findFieldsToProcess(clazz).forEach(field -> {
6✔
90
            Property<?> property = getPropertyField(field);
4✔
91
            if (property != null) {
2✔
92
                propertyListBuilder.add(property);
4✔
93
                setCommentForPropertyField(field, property.getPath());
5✔
94
            }
95
        });
1✔
96
    }
1✔
97

98
    protected void setCommentForPropertyField(@NotNull Field field, @NotNull String path) {
99
        Comment commentAnnotation = field.getAnnotation(Comment.class);
5✔
100
        if (commentAnnotation != null) {
2✔
101
            commentsConfiguration.setComment(path, commentAnnotation.value());
6✔
102
        }
103
    }
1✔
104

105
    /**
106
     * Returns the given field's value if it is a static {@link Property}.
107
     *
108
     * @param field the field's value to return
109
     * @return the property the field defines, or null if not applicable
110
     */
111
    protected @Nullable Property<?> getPropertyField(@NotNull Field field) {
112
        if (Property.class.isAssignableFrom(field.getType()) && Modifier.isStatic(field.getModifiers())) {
9✔
113
            try {
114
                setFieldAccessibleIfNeeded(field);
3✔
115
                return (Property<?>) field.get(null);
5✔
116
            } catch (IllegalAccessException e) {
1✔
117
                throw new ConfigMeException("Could not fetch field '" + field.getName() + "' from class '"
13✔
118
                    + field.getDeclaringClass().getSimpleName() + "'. Is it maybe not public?", e);
9✔
119
            }
120
        }
121
        return null;
2✔
122
    }
123

124
    /**
125
     * Sets the Field object to be accessible if needed; this makes the code support constants in Kotlin without the
126
     * {@code @JvmField} annotation. This method only calls {@link Field#setAccessible} if it is needed to avoid
127
     * potential issues with a security manager. Within Java itself, only {@code public static final} property fields
128
     * are expected.
129
     *
130
     * @param field the field to process
131
     */
132
    protected void setFieldAccessibleIfNeeded(@NotNull Field field) {
133
        try {
134
            if (!Modifier.isPublic(field.getModifiers())) {
4✔
135
                field.setAccessible(true);
3✔
136
            }
137
        } catch (Exception e) {
×
138
            throw new ConfigMeException("Failed to modify access for field '" + field.getName() + "' from class '"
×
139
                + field.getDeclaringClass().getSimpleName() + "'", e);
×
140
        }
1✔
141
    }
1✔
142

143
    protected void collectSectionComments(@NotNull Class<? extends SettingsHolder> clazz) {
144
        SettingsHolder settingsHolder = createSettingsHolderInstance(clazz);
4✔
145
        settingsHolder.registerComments(commentsConfiguration);
4✔
146
    }
1✔
147

148
    /**
149
     * Creates an instance of the given settings holder class.
150
     *
151
     * @param clazz the class to instantiate
152
     * @param <T> the class type
153
     * @return instance of the class
154
     */
155
    protected <T extends SettingsHolder> @NotNull T createSettingsHolderInstance(@NotNull Class<T> clazz) {
156
        try {
157
            Constructor<T> constructor = clazz.getDeclaredConstructor();
5✔
158
            constructor.setAccessible(true);
3✔
159
            return constructor.newInstance();
6✔
160
        } catch (NoSuchMethodException e) {
1✔
161
            throw new ConfigMeException("Expected no-args constructor to be available for " + clazz, e);
13✔
162
        } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
1✔
163
            throw new ConfigMeException("Could not create instance of " + clazz, e);
13✔
164
        }
165
    }
166

167
    /**
168
     * Returns all fields of the class which should be considered as potential {@link Property} definitions.
169
     * Considers the class's parents.
170
     *
171
     * @param clazz the class whose fields should be returned
172
     * @return stream of all the fields to process
173
     */
174
    protected @NotNull Stream<Field> findFieldsToProcess(@NotNull Class<?> clazz) {
175
        // In most cases we expect the class not to have any parent, so we check here and "fast track" this case
176
        if (Object.class.equals(clazz.getSuperclass())) {
5✔
177
            return Arrays.stream(clazz.getDeclaredFields());
4✔
178
        }
179

180
        List<Class<?>> classes = new ArrayList<>();
4✔
181
        Class<?> currentClass = clazz;
2✔
182
        while (currentClass != null && !currentClass.equals(Object.class)) {
6✔
183
            classes.add(currentClass);
4✔
184
            currentClass = currentClass.getSuperclass();
4✔
185
        }
186
        Collections.reverse(classes);
2✔
187

188
        return classes.stream()
4✔
189
            .map(Class::getDeclaredFields)
2✔
190
            .flatMap(Arrays::stream);
1✔
191
    }
192
}
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