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

AuthMe / ConfigMe / 5826222367

10 Aug 2023 09:20PM UTC coverage: 99.39% (-0.2%) from 99.591%
5826222367

Pull #348

github

ljacqu
#133 Allow private fields as properties
- Use Field#setAccessible if a field is not private to make the code compatible with Kotlin constants without the need of the JvmField annotation.
Pull Request #348: #133 Allow private fields as properties

566 of 582 branches covered (97.25%)

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

1467 of 1476 relevant lines covered (99.39%)

4.58 hits per line

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

96.15
/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 retrieving all {@link Property} fields
22
 * from {@link SettingsHolder} implementations via reflection.
23
 * <p>
24
 * Properties must be declared as {@code public static} fields or they are ignored.
25
 */
26
public class ConfigurationDataBuilder {
27

28
    @SuppressWarnings("checkstyle:VisibilityModifier")
5✔
29
    protected @NotNull PropertyListBuilder propertyListBuilder = new PropertyListBuilder();
30
    @SuppressWarnings("checkstyle:VisibilityModifier")
5✔
31
    protected @NotNull CommentsConfiguration commentsConfiguration = new CommentsConfiguration();
32

33
    protected ConfigurationDataBuilder() {
2✔
34
    }
1✔
35

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

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

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

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

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

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

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

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

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

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

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

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

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

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

© 2026 Coveralls, Inc