9 | * Declare your properties as {@code public static final} fields
10 | * of {@link ch.jalu.configme.properties.Property} type in a class
11 | * which implements this interface.
12 | *
13 | * Classes implementing this interface must have a no-args constructor (any visibility).
14 | *
15 | * @see ch.jalu.configme.properties.PropertyInitializer
16 | * @see ch.jalu.configme.configurationdata.ConfigurationDataBuilder
17 | */
18 | public interface SettingsHolder {
19 |
20 | /**
21 | * Allows to register comments for sections and properties by overriding this method and interacting
22 | * with the given configuration object.
23 | *
24 | * Note that comments can also be put on Property fields with the {@link Comment} annotation.
25 | *
26 | * @param conf the comments configuration
27 | */
28 | default void registerComments(@NotNull CommentsConfiguration conf) {
29 | // override to register comments for sections
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/SettingsManager.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme;
2 |
3 | import ch.jalu.configme.migration.MigrationService;
4 | import ch.jalu.configme.properties.Property;
5 | import org.jetbrains.annotations.NotNull;
6 |
7 | /**
8 | * Settings manager.
9 | *
10 | * The settings manager manages a {@link ch.jalu.configme.resource.PropertyResource property resource},
11 | * {@link ch.jalu.configme.configurationdata.ConfigurationData configuration data}, and an optional
12 | * {@link MigrationService migration service}.
13 | *
14 | * The settings manager allows to look up and modify properties. After it is initialized, the settings manager
15 | * should be the only class from ConfigMe that developers need to interact with.
16 | *
17 | * @see ConfigMe on Github
18 | * @see SettingsManagerBuilder
19 | */
20 | public interface SettingsManager {
21 |
22 | /**
23 | * Gets the given property from the configuration.
24 | *
25 | * @param property the property to retrieve
26 | * @param the property's type
27 | * @return the property's value
28 | */
29 | @NotNull T getProperty(@NotNull Property property);
30 |
31 | /**
32 | * Sets a new value for the given property.
33 | *
34 | * @param property the property to modify
35 | * @param value the new value to assign to the property
36 | * @param the property's type
37 | */
38 | void setProperty(@NotNull Property property, @NotNull T value);
39 |
40 | /**
41 | * Reloads the configuration from the property resource.
42 | */
43 | void reload();
44 |
45 | /**
46 | * Saves the properties to the configuration file.
47 | */
48 | void save();
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/beanmapper/ConfigMeMapperException.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.beanmapper;
2 |
3 | import ch.jalu.configme.beanmapper.context.MappingContext;
4 | import ch.jalu.configme.exception.ConfigMeException;
5 | import org.jetbrains.annotations.NotNull;
6 | import org.jetbrains.annotations.Nullable;
7 |
8 | /**
9 | * Exception during a bean mapping process.
10 | */
11 | public class ConfigMeMapperException extends ConfigMeException {
12 |
13 | private static final long serialVersionUID = 5439842847683269906L;
14 |
15 | /**
16 | * Constructor.
17 | *
18 | * @param message the exception message
19 | */
20 | public ConfigMeMapperException(@NotNull String message) {
21 | super(message);
22 | }
23 |
24 | /**
25 | * Constructor.
26 | *
27 | * @param message the exception message
28 | * @param cause the cause
29 | */
30 | public ConfigMeMapperException(@NotNull String message, @Nullable Throwable cause) {
31 | super(message, cause);
32 | }
33 |
34 | /**
35 | * Constructor.
36 | *
37 | * @param mappingContext the mapping context with which the message should be extended
38 | * @param message basic message to extend
39 | */
40 | public ConfigMeMapperException(@NotNull MappingContext mappingContext, @NotNull String message) {
41 | super(constructMessage(mappingContext, message));
42 | }
43 |
44 | /**
45 | * Constructor.
46 | *
47 | * @param mappingContext the mapping context with which the message should be extended
48 | * @param message basic message to extend
49 | * @param cause the cause
50 | */
51 | public ConfigMeMapperException(@NotNull MappingContext mappingContext, @NotNull String message,
52 | @Nullable Throwable cause) {
53 | super(constructMessage(mappingContext, message), cause);
54 | }
55 |
56 | private static @NotNull String constructMessage(@NotNull MappingContext mappingContext, @NotNull String message) {
57 | return message + ", for mapping of: [" + mappingContext.createDescription() + "]";
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/beanmapper/DefaultMapper.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.beanmapper;
2 |
3 | import org.jetbrains.annotations.NotNull;
4 |
5 | /**
6 | * Provides the {@link Mapper} instance which is used by default.
7 | */
8 | public final class DefaultMapper extends MapperImpl {
9 |
10 | private static DefaultMapper instance;
11 |
12 | private DefaultMapper() {
13 | }
14 |
15 | /**
16 | * @return default mapper instance
17 | */
18 | public static @NotNull Mapper getInstance() {
19 | if (instance == null) {
20 | instance = new DefaultMapper();
21 | }
22 | return instance;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/beanmapper/ExportName.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.beanmapper;
2 |
3 | import org.jetbrains.annotations.NotNull;
4 |
5 | import java.lang.annotation.Documented;
6 | import java.lang.annotation.ElementType;
7 | import java.lang.annotation.Retention;
8 | import java.lang.annotation.RetentionPolicy;
9 | import java.lang.annotation.Target;
10 |
11 | /**
12 | * Annotation placed on a field to indicate that it should be loaded and written
13 | * to a property resource with a different name.
14 | */
15 | @Retention(RetentionPolicy.RUNTIME)
16 | @Target(ElementType.FIELD)
17 | @Documented
18 | public @interface ExportName {
19 |
20 | /**
21 | * @return the name to use when interacting with property resources
22 | */
23 | @NotNull String value();
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/beanmapper/Mapper.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.beanmapper;
2 |
3 | import ch.jalu.configme.properties.convertresult.ConvertErrorRecorder;
4 | import ch.jalu.typeresolver.TypeInfo;
5 | import org.jetbrains.annotations.NotNull;
6 | import org.jetbrains.annotations.Nullable;
7 |
8 | /**
9 | * Creates JavaBeans based on the values coming from a property reader. See the JavaDoc on the default implementation,
10 | * {@link MapperImpl}, for more details.
11 | */
12 | public interface Mapper {
13 |
14 | /**
15 | * Creates an object of the given type from the given value. Returns null if the
16 | * conversion is not possible. The value usually stems from a property resource and
17 | * is a Map of values.
18 | *
19 | * @param value the value to convert (typically a Map)
20 | * @param targetType the required type
21 | * @param errorRecorder error recorder to register errors even if a valid value is returned
22 | * @return object of the given type, or null if not possible
23 | */
24 | @Nullable Object convertToBean(@Nullable Object value, @NotNull TypeInfo targetType,
25 | @NotNull ConvertErrorRecorder errorRecorder);
26 |
27 | /**
28 | * Converts the given value to an object of the given class, if possible. Returns null otherwise.
29 | * This is a convenience method as typed alternative to
30 | * {@link #convertToBean(Object, TypeInfo, ConvertErrorRecorder)}.
31 | *
32 | * @param value the value to convert (typically a Map)
33 | * @param clazz the required class
34 | * @param errorRecorder error recorder to register errors even if a valid value is returned
35 | * @param the class type
36 | * @return object of the given type, or null if not possible
37 | */
38 | @SuppressWarnings("unchecked")
39 | default @Nullable T convertToBean(@Nullable Object value, @NotNull Class clazz,
40 | @NotNull ConvertErrorRecorder errorRecorder) {
41 | return (T) convertToBean(value, new TypeInfo(clazz), errorRecorder);
42 | }
43 |
44 | /**
45 | * Converts a complex type such as a JavaBean object to simple types suitable for exporting. This method
46 | * typically returns a Map of values, or simple types like String / Number for scalar values.
47 | * Used in the {@link ch.jalu.configme.properties.BeanProperty#toExportValue} method.
48 | *
49 | * @param object the object to convert to its export value
50 | * @return export value to use
51 | */
52 | @Nullable Object toExportValue(@NotNull Object object);
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/beanmapper/context/ExportContext.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.beanmapper.context;
2 |
3 | import ch.jalu.configme.beanmapper.propertydescription.BeanPropertyComments;
4 | import org.jetbrains.annotations.NotNull;
5 |
6 | /**
7 | * Context used by the bean mapper when a value is exported.
8 | */
9 | public interface ExportContext {
10 |
11 | /**
12 | * @return path relative to the bean root that is currently being processed
13 | */
14 | @NotNull String getBeanPath();
15 |
16 | /**
17 | * Creates a child context with the given path as addition to this context's path.
18 | *
19 | * @param path the path to add
20 | * @return child export context
21 | */
22 | @NotNull ExportContext createChildContext(@NotNull String path);
23 |
24 | /**
25 | * Specifies whether the given comments instance should be included in the export in this context. Comments
26 | * should not be included if they're specified to appear only once and they've already been incorporated.
27 | *
28 | * @param comments the comments instance to process
29 | * @return true if the comments should be included, false if they should be skipped
30 | */
31 | boolean shouldInclude(@NotNull BeanPropertyComments comments);
32 |
33 | /**
34 | * Registers the given comments. Used to keep track of all unique comment's UUIDs that have been processed.
35 | *
36 | * @param comments the comments instance to process
37 | */
38 | void registerComment(@NotNull BeanPropertyComments comments);
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/beanmapper/context/ExportContextImpl.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.beanmapper.context;
2 |
3 | import ch.jalu.configme.beanmapper.propertydescription.BeanPropertyComments;
4 | import ch.jalu.configme.internal.PathUtils;
5 |
6 | import org.jetbrains.annotations.NotNull;
7 |
8 | import java.util.HashSet;
9 | import java.util.Set;
10 | import java.util.UUID;
11 |
12 | /**
13 | * Standard implementation of {@link ExportContext}.
14 | */
15 | public class ExportContextImpl implements ExportContext {
16 |
17 | private final String beanPath;
18 | private final Set usedUniqueCommentIds;
19 |
20 | /**
21 | * Constructor.
22 | *
23 | * @param beanPath path relative to the bean root
24 | * @param usedUniqueCommentIds set of unique comment UUIDs that have already been used
25 | */
26 | protected ExportContextImpl(@NotNull String beanPath, @NotNull Set usedUniqueCommentIds) {
27 | this.beanPath = beanPath;
28 | this.usedUniqueCommentIds = usedUniqueCommentIds;
29 | }
30 |
31 | /**
32 | * Creates an initial context for the export of a bean value.
33 | *
34 | * @return root export context
35 | */
36 | public static @NotNull ExportContextImpl createRoot() {
37 | return new ExportContextImpl("", new HashSet<>());
38 | }
39 |
40 | @Override
41 | public @NotNull ExportContext createChildContext(@NotNull String path) {
42 | String childPath = PathUtils.concatSpecifierAware(beanPath, path);
43 | return new ExportContextImpl(childPath, usedUniqueCommentIds);
44 | }
45 |
46 | @Override
47 | public @NotNull String getBeanPath() {
48 | return beanPath;
49 | }
50 |
51 | @Override
52 | public boolean shouldInclude(@NotNull BeanPropertyComments comments) {
53 | return !comments.getComments().isEmpty()
54 | && (comments.getUuid() == null || !usedUniqueCommentIds.contains(comments.getUuid()));
55 | }
56 |
57 | @Override
58 | public void registerComment(@NotNull BeanPropertyComments comments) {
59 | if (comments.getUuid() != null) {
60 | usedUniqueCommentIds.add(comments.getUuid());
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/beanmapper/context/MappingContextImpl.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.beanmapper.context;
2 |
3 | import ch.jalu.configme.internal.PathUtils;
4 | import ch.jalu.configme.properties.convertresult.ConvertErrorRecorder;
5 | import ch.jalu.typeresolver.TypeInfo;
6 | import org.jetbrains.annotations.NotNull;
7 |
8 | /**
9 | * Standard implementation of {@link MappingContext}.
10 | */
11 | public class MappingContextImpl implements MappingContext {
12 |
13 | private final String beanPath;
14 | private final TypeInfo targetType;
15 | private final ConvertErrorRecorder errorRecorder;
16 |
17 | protected MappingContextImpl(@NotNull String beanPath, @NotNull TypeInfo targetType,
18 | @NotNull ConvertErrorRecorder errorRecorder) {
19 | this.beanPath = beanPath;
20 | this.targetType = targetType;
21 | this.errorRecorder = errorRecorder;
22 | }
23 |
24 | /**
25 | * Creates an initial context (used at the start of a mapping process).
26 | *
27 | * @param targetType the required type
28 | * @param errorRecorder error recorder to register errors even if a valid value is returned
29 | * @return root mapping context
30 | */
31 | public static @NotNull MappingContextImpl createRoot(@NotNull TypeInfo targetType,
32 | @NotNull ConvertErrorRecorder errorRecorder) {
33 | return new MappingContextImpl("", targetType, errorRecorder);
34 | }
35 |
36 | @Override
37 | public @NotNull MappingContext createChild(@NotNull String subPath, @NotNull TypeInfo targetType) {
38 | String childPath = PathUtils.concatSpecifierAware(beanPath, subPath);
39 | return new MappingContextImpl(childPath, targetType, errorRecorder);
40 | }
41 |
42 | public @NotNull String getBeanPath() {
43 | return beanPath;
44 | }
45 |
46 | @Override
47 | public @NotNull TypeInfo getTargetType() {
48 | return targetType;
49 | }
50 |
51 | @Override
52 | public @NotNull String createDescription() {
53 | return "Bean path: '" + beanPath + "', type: '" + targetType.getType() + "'";
54 | }
55 |
56 | @Override
57 | public @NotNull ConvertErrorRecorder getErrorRecorder() {
58 | return errorRecorder;
59 | }
60 |
61 | @Override
62 | public @NotNull String toString() {
63 | return getClass().getSimpleName() + "[" + createDescription() + "]";
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/beanmapper/leafvaluehandler/EnumLeafType.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.beanmapper.leafvaluehandler;
2 |
3 | import ch.jalu.configme.properties.convertresult.ConvertErrorRecorder;
4 | import ch.jalu.typeresolver.EnumUtils;
5 | import ch.jalu.typeresolver.TypeInfo;
6 | import org.jetbrains.annotations.NotNull;
7 | import org.jetbrains.annotations.Nullable;
8 |
9 | /**
10 | * Handles enum type conversions for the bean mapper.
11 | */
12 | public class EnumLeafType implements MapperLeafType {
13 |
14 | @Override
15 | public @Nullable Object convert(@Nullable Object value, @NotNull TypeInfo targetType,
16 | @NotNull ConvertErrorRecorder errorRecorder) {
17 | if (value instanceof String) {
18 | return EnumUtils.asEnumClassIfPossible(targetType.toClass())
19 | .flatMap(clz -> EnumUtils.tryValueOfCaseInsensitive(clz, (String) value))
20 | .orElse(null);
21 | }
22 | return null;
23 | }
24 |
25 | @Override
26 | public @Nullable Object toExportValueIfApplicable(@Nullable Object value) {
27 | if (value instanceof Enum>) {
28 | return ((Enum>) value).name();
29 | }
30 | return null;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/beanmapper/leafvaluehandler/LeafValueHandler.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.beanmapper.leafvaluehandler;
2 |
3 | import ch.jalu.configme.beanmapper.context.ExportContext;
4 | import ch.jalu.configme.beanmapper.context.MappingContext;
5 | import org.jetbrains.annotations.NotNull;
6 | import org.jetbrains.annotations.Nullable;
7 |
8 | /**
9 | * The leaf value handler is used in {@link ch.jalu.configme.beanmapper.MapperImpl} to convert "simple" values that
10 | * were read from a resource to its desired type.
11 | *
12 | * The bean mapper handles complex types such as maps and collections, and recursively calls the bean mapping process
13 | * accordingly. This class complements the mapper by halting this process and providing "leaf values" to be set into
14 | * Java beans.
15 | *
16 | * The default implementation is {@link LeafValueHandlerImpl}. With the aid of {@link MapperLeafType} entries, it
17 | * provides conversions for the leaf types it supports. To implement additional conversions, implement your own
18 | * {@link MapperLeafType} implementations and add them to {@link LeafValueHandlerImpl}. Alternatively, you can create
19 | * your own implementation of this interface if you need more control over the conversion process.
20 | */
21 | public interface LeafValueHandler {
22 |
23 | /**
24 | * Converts the given value to the target type (as defined by the mapping context), if supported. Otherwise,
25 | * null is returned. If a value is returned, its type is guaranteed to match the target type.
26 | *
27 | * @param value the value to convert (read from a property resource)
28 | * @param mappingContext mapping context with the target type
29 | * @return the converted value, or null if not applicable
30 | */
31 | @Nullable Object convert(@Nullable Object value, @NotNull MappingContext mappingContext);
32 |
33 | /**
34 | * Converts the value of a property to a value suitable for exporting. This method converts the opposite
35 | * way of {@link #convert}. Null is returned if this leaf value handler does not support the object's type.
36 | *
37 | * @param value the value to convert
38 | * @param exportContext the export context (usually not needed)
39 | * @return the value suitable for exporting, or null if not applicable
40 | */
41 | @Nullable Object toExportValue(@Nullable Object value, @NotNull ExportContext exportContext);
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/beanmapper/leafvaluehandler/MapperLeafType.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.beanmapper.leafvaluehandler;
2 |
3 | import ch.jalu.configme.properties.convertresult.ConvertErrorRecorder;
4 | import ch.jalu.typeresolver.TypeInfo;
5 | import org.jetbrains.annotations.NotNull;
6 | import org.jetbrains.annotations.Nullable;
7 |
8 | /**
9 | * A leaf type represents a "simple" type like a String or a number. Used by the bean mapper's
10 | * {@link LeafValueHandlerImpl}, a leaf type provides a simple conversion for a specific type.
11 | *
12 | * @see LeafValueHandler
13 | */
14 | public interface MapperLeafType {
15 |
16 | /**
17 | * Converts the given value to the specified target type, if possible. Returns null otherwise.
18 | * This method must either return {@code null} or an object of the given target type.
19 | *
20 | * @param value the value to convert
21 | * @param targetType the required type
22 | * @param errorRecorder error recorder to register errors even if a valid value is returned
23 | * @return value of the given type, or null if not applicable
24 | */
25 | @Nullable Object convert(@Nullable Object value, @NotNull TypeInfo targetType,
26 | @NotNull ConvertErrorRecorder errorRecorder);
27 |
28 | /**
29 | * Converts the given value to a type suitable for exporting. Used by the mapper
30 | * when {@link ch.jalu.configme.beanmapper.MapperImpl#toExportValue(Object)} is called.
31 | * Returns null if the leaf value handler cannot handle the value.
32 | *
33 | * Return {@link ch.jalu.configme.beanmapper.MapperImpl#RETURN_NULL} to signal that null should be used
34 | * as the export value (returning {@code null} itself means this leaf value handler cannot handle it).
35 | *
36 | * @param value the value to convert to an export value, if possible
37 | * @return value to use in the export, or null if not applicable
38 | */
39 | @Nullable Object toExportValueIfApplicable(@Nullable Object value);
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/beanmapper/propertydescription/BeanDescriptionFactory.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.beanmapper.propertydescription;
2 |
3 | import org.jetbrains.annotations.NotNull;
4 |
5 | import java.util.Collection;
6 |
7 | /**
8 | * Factory which analyzes a class and returns all writable properties.
9 | *
10 | * Default implementation: {@link BeanDescriptionFactoryImpl}.
11 | */
12 | public interface BeanDescriptionFactory {
13 |
14 | /**
15 | * Returns all properties on the given class which should be considered while creating a bean of the
16 | * given type. This is usually all properties which can be read from and written to.
17 | *
18 | * @param clazz the class whose properties should be returned
19 | * @return the relevant properties on the class
20 | */
21 | @NotNull Collection getAllProperties(@NotNull Class> clazz);
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/beanmapper/propertydescription/BeanPropertyComments.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.beanmapper.propertydescription;
2 |
3 | import org.jetbrains.annotations.NotNull;
4 | import org.jetbrains.annotations.Nullable;
5 |
6 | import java.util.Collections;
7 | import java.util.List;
8 | import java.util.UUID;
9 |
10 | /**
11 | * Contains the comments for a bean property, with a UUID if the comment should only be included once.
12 | */
13 | public class BeanPropertyComments {
14 |
15 | /** Instance which can be used if there are no comments to add. */
16 | public static final BeanPropertyComments EMPTY = new BeanPropertyComments(Collections.emptyList(), null);
17 |
18 | private final List comments;
19 | private final UUID uuid;
20 |
21 | /**
22 | * Constructor.
23 | *
24 | * @param comments the comments
25 | * @param uuid UUID to identify the comment with, null if the comment should be repeated
26 | */
27 | public BeanPropertyComments(@NotNull List comments, @Nullable UUID uuid) {
28 | this.comments = comments;
29 | this.uuid = uuid;
30 | }
31 |
32 | public @NotNull List getComments() {
33 | return comments;
34 | }
35 |
36 | /**
37 | * UUID to identify this comment. Not-null when the comment should not be repeated if it could be part of the
38 | * export multiple times.
39 | *
40 | * @return UUID if the comment should be unique, null otherwise
41 | */
42 | public @Nullable UUID getUuid() {
43 | return uuid;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/beanmapper/propertydescription/BeanPropertyDescription.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.beanmapper.propertydescription;
2 |
3 | import ch.jalu.typeresolver.TypeInfo;
4 | import org.jetbrains.annotations.NotNull;
5 | import org.jetbrains.annotations.Nullable;
6 |
7 | /**
8 | * Represents a property within a bean class, as used in {@link ch.jalu.configme.beanmapper.MapperImpl}.
9 | * There, for instance, there is a {@link BeanDescriptionFactory} field responsible for creating bean descriptions.
10 | *
11 | * Default implementation is {@link BeanPropertyDescriptionImpl}.
12 | */
13 | public interface BeanPropertyDescription {
14 |
15 | /**
16 | * @return the name of the property in the configuration file
17 | */
18 | @NotNull String getName();
19 |
20 | /**
21 | * @return property type
22 | */
23 | @NotNull TypeInfo getTypeInformation();
24 |
25 | /**
26 | * Sets the given value on the provided bean for this property. The value should correspond
27 | * to the {@link #getTypeInformation() property type}.
28 | *
29 | * @param bean the bean to set the property on
30 | * @param value the value to set
31 | */
32 | void setValue(@NotNull Object bean, @NotNull Object value);
33 |
34 | /**
35 | * Returns the value of the property for the given bean.
36 | *
37 | * @param bean the bean to read the property from
38 | * @return the value of the property (can be null)
39 | */
40 | @Nullable Object getValue(@NotNull Object bean);
41 |
42 | /**
43 | * @return the comments associated with this property
44 | */
45 | @NotNull BeanPropertyComments getComments();
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/configurationdata/CommentsConfiguration.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.configurationdata;
2 |
3 | import ch.jalu.configme.Comment;
4 | import ch.jalu.configme.SettingsHolder;
5 | import org.jetbrains.annotations.NotNull;
6 | import org.jetbrains.annotations.UnmodifiableView;
7 |
8 | import java.util.Arrays;
9 | import java.util.Collections;
10 | import java.util.HashMap;
11 | import java.util.List;
12 | import java.util.Map;
13 |
14 | /**
15 | * Allows to register comments (intended via {@link SettingsHolder#registerComments}).
16 | */
17 | public class CommentsConfiguration {
18 |
19 | private final @NotNull Map> comments;
20 |
21 | /**
22 | * Constructor.
23 | */
24 | public CommentsConfiguration() {
25 | this.comments = new HashMap<>();
26 | }
27 |
28 | /**
29 | * Constructor.
30 | *
31 | * @param comments map to store comments in
32 | */
33 | public CommentsConfiguration(@NotNull Map> comments) {
34 | this.comments = comments;
35 | }
36 |
37 | /**
38 | * Sets the given lines for the provided path, overriding any previously existing comments for the path.
39 | * An entry that is a sole new-line (i.e. "\n") will result in an empty line without any comment marker.
40 | *
41 | * @param path the path to register the comment lines for
42 | * @param commentLines the comment lines to set for the path
43 | */
44 | public void setComment(@NotNull String path, @NotNull String... commentLines) {
45 | List replaced = comments.put(path, Collections.unmodifiableList(Arrays.asList(commentLines)));
46 |
47 | if (replaced != null) {
48 | String commentAnnotation = "@" + Comment.class.getSimpleName();
49 | throw new IllegalStateException("Comments for path '" + path + "' have already been registered. Use "
50 | + commentAnnotation + " on a property field, or one call to CommentsConfiguration#setComment per path");
51 | }
52 | }
53 |
54 | /**
55 | * Returns a read-only view of the map with all comments.
56 | *
57 | * @return map with all comments
58 | */
59 | public @NotNull @UnmodifiableView Map> getAllComments() {
60 | return Collections.unmodifiableMap(comments);
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/exception/ConfigMeException.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.exception;
2 |
3 | import org.jetbrains.annotations.NotNull;
4 | import org.jetbrains.annotations.Nullable;
5 |
6 | /**
7 | * ConfigMe exception.
8 | */
9 | public class ConfigMeException extends RuntimeException {
10 |
11 | private static final long serialVersionUID = -865062331853823084L;
12 |
13 | public ConfigMeException(@NotNull String message) {
14 | super(message);
15 | }
16 |
17 | public ConfigMeException(@NotNull String message, @Nullable Throwable cause) {
18 | super(message, cause);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/internal/StreamUtils.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.internal;
2 |
3 | import org.jetbrains.annotations.NotNull;
4 |
5 | import java.util.stream.IntStream;
6 | import java.util.stream.Stream;
7 |
8 | /**
9 | * Stream utils.
10 | */
11 | public final class StreamUtils {
12 |
13 | private StreamUtils() {
14 | }
15 |
16 | /**
17 | * Creates a stream with the requested size. Every element in the stream is the given {@code element}.
18 | *
19 | * @param element the element to repeat
20 | * @param numberOfTimes number of times the stream should have the element
21 | * @param element type
22 | * @return stream with the element the requested number of times
23 | */
24 | public static @NotNull Stream repeat(@NotNull T element, int numberOfTimes) {
25 | return IntStream.range(0, numberOfTimes)
26 | .mapToObj(i -> element);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/migration/MigrationService.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.migration;
2 |
3 | import ch.jalu.configme.configurationdata.ConfigurationData;
4 | import ch.jalu.configme.resource.PropertyReader;
5 | import org.jetbrains.annotations.NotNull;
6 |
7 | /**
8 | * The migration service is called when the settings manager is instantiated. It allows to
9 | * validate the settings and perform migrations (e.g. delete old settings, rename settings).
10 | * If a migration is performed, the config file will be saved again.
11 | */
12 | public interface MigrationService {
13 |
14 | /** Constant for the return value of {@link #checkAndMigrate}, indicating that a migration has been performed. */
15 | boolean MIGRATION_REQUIRED = true;
16 |
17 | /** Constant for the return value of {@link #checkAndMigrate}, indicating that no migration was needed. */
18 | boolean NO_MIGRATION_NEEDED = false;
19 |
20 | /**
21 | * Performs the migration, returning whether a migration has been performed or not.
22 | *
23 | * @param reader reader to access the values in the configuration file
24 | * @param configurationData configuration data, which knows all properties and manages their associated values
25 | * @return true if a migration has been performed, false otherwise. Indicates whether the configuration data should
26 | * be saved to the configuration file or not
27 | */
28 | boolean checkAndMigrate(@NotNull PropertyReader reader, @NotNull ConfigurationData configurationData);
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/migration/PlainMigrationService.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.migration;
2 |
3 | import ch.jalu.configme.configurationdata.ConfigurationData;
4 | import ch.jalu.configme.resource.PropertyReader;
5 | import org.jetbrains.annotations.NotNull;
6 |
7 | /**
8 | * Simple migration service that can be extended.
9 | */
10 | public class PlainMigrationService implements MigrationService {
11 |
12 | @Override
13 | public boolean checkAndMigrate(@NotNull PropertyReader reader, @NotNull ConfigurationData configurationData) {
14 | if (performMigrations(reader, configurationData) == MIGRATION_REQUIRED
15 | || !configurationData.areAllValuesValidInResource()) {
16 | return MIGRATION_REQUIRED;
17 | }
18 | return NO_MIGRATION_NEEDED;
19 | }
20 |
21 | /**
22 | * Override this method for custom migrations. This method is executed before checking
23 | * if all settings are present. For instance, you could implement deleting obsolete properties
24 | * and rename properties in this method.
25 | *
26 | * Note that the settings manager automatically saves the resource
27 | * if the migration service returns {@link #MIGRATION_REQUIRED} from {@link #checkAndMigrate}.
28 | *
29 | * @param reader the reader with which the configuration file can be read
30 | * @param configurationData the configuration data
31 | * @return true if a migration has been performed, false otherwise (see constants on {@link MigrationService})
32 | */
33 | protected boolean performMigrations(@NotNull PropertyReader reader, @NotNull ConfigurationData configurationData) {
34 | return NO_MIGRATION_NEEDED;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/migration/version/VersionMigration.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.migration.version;
2 |
3 | import ch.jalu.configme.configurationdata.ConfigurationData;
4 | import ch.jalu.configme.resource.PropertyReader;
5 | import org.jetbrains.annotations.NotNull;
6 |
7 | /**
8 | * A migration used by {@link VersionMigrationService} to migrate from one configuration version to a newer one.
9 | *
10 | * @see VersionMigrationService
11 | */
12 | public interface VersionMigration {
13 |
14 | /**
15 | * @return the configuration version this migration converts from (e.g. 1)
16 | */
17 | int fromVersion();
18 |
19 | /**
20 | * @return the configuration version this migration converts to (e.g. 2)
21 | */
22 | int targetVersion();
23 |
24 | /**
25 | * Migrates the configuration.
26 | *
27 | * @param reader the property reader to read the configuration file from
28 | * @param configurationData configuration data to update a property's value
29 | */
30 | void migrate(@NotNull PropertyReader reader, @NotNull ConfigurationData configurationData);
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/properties/ArrayProperty.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.properties;
2 |
3 | import ch.jalu.configme.properties.types.ArrayPropertyType;
4 | import ch.jalu.configme.properties.types.PropertyType;
5 | import org.jetbrains.annotations.NotNull;
6 |
7 | import java.util.function.IntFunction;
8 |
9 | /**
10 | * Property whose value is an array of a given type.
11 | *
12 | * @param the type of the elements in the array
13 | */
14 | public class ArrayProperty extends TypeBasedProperty {
15 |
16 | /**
17 | * Constructor.
18 | *
19 | * @param path the path of the property
20 | * @param entryType the property type the elements of the arrays have
21 | * @param arrayProducer array constructor (desired array size as argument)
22 | * @param defaultValue the default value of the property
23 | */
24 | public ArrayProperty(@NotNull String path, @NotNull PropertyType entryType,
25 | @NotNull IntFunction arrayProducer, T @NotNull [] defaultValue) {
26 | this(path, new ArrayPropertyType<>(entryType, arrayProducer), defaultValue);
27 | }
28 |
29 | /**
30 | * Constructor.
31 | *
32 | * @param path the path of the property
33 | * @param type the type of this property (e.g. {@link ArrayPropertyType})
34 | * @param defaultValue the default value of the property
35 | */
36 | @SafeVarargs
37 | public ArrayProperty(@NotNull String path, @NotNull PropertyType type, T @NotNull ... defaultValue) {
38 | super(path, type, defaultValue);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/properties/BaseProperty.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.properties;
2 |
3 | import ch.jalu.configme.properties.convertresult.ConvertErrorRecorder;
4 | import ch.jalu.configme.properties.convertresult.PropertyValue;
5 | import ch.jalu.configme.resource.PropertyReader;
6 | import org.jetbrains.annotations.NotNull;
7 |
8 | import org.jetbrains.annotations.Nullable;
9 | import java.util.Objects;
10 |
11 | /**
12 | * Base implementation of {@link Property}. All properties should extend from this class.
13 | *
14 | * This base implementation makes interacting with properties null safe by guaranteeing that the default value
15 | * and its {@link #determineValue determined value} can never be null.
16 | *
17 | * @param the property type
18 | */
19 | public abstract class BaseProperty implements Property {
20 |
21 | private final String path;
22 | private final T defaultValue;
23 |
24 | /**
25 | * Constructor.
26 | *
27 | * @param path the path of the property
28 | * @param defaultValue the default value of the property
29 | */
30 | public BaseProperty(@NotNull String path, @NotNull T defaultValue) {
31 | Objects.requireNonNull(path, "path");
32 | Objects.requireNonNull(defaultValue, "defaultValue");
33 | this.path = path;
34 | this.defaultValue = defaultValue;
35 | }
36 |
37 | @Override
38 | public @NotNull String getPath() {
39 | return path;
40 | }
41 |
42 | @Override
43 | public @NotNull T getDefaultValue() {
44 | return defaultValue;
45 | }
46 |
47 | @Override
48 | public @NotNull PropertyValue determineValue(@NotNull PropertyReader reader) {
49 | ConvertErrorRecorder errorRecorder = new ConvertErrorRecorder();
50 | T value = getFromReader(reader, errorRecorder);
51 | if (isValidValue(value)) {
52 | return new PropertyValue<>(value, errorRecorder.isFullyValid());
53 | }
54 | return PropertyValue.withValueRequiringRewrite(getDefaultValue());
55 | }
56 |
57 | @Override
58 | public boolean isValidValue(@Nullable T value) {
59 | return value != null;
60 | }
61 |
62 | /**
63 | * Constructs the value of the property from the property reader. Returns null if no value is
64 | * available in the reader or if it cannot be used to construct a value for this property.
65 | *
66 | * @param reader the reader to read from
67 | * @param errorRecorder error recorder to register errors even if a valid value is returned
68 | * @return value based on the reader, or null if not applicable
69 | */
70 | protected abstract @Nullable T getFromReader(@NotNull PropertyReader reader,
71 | @NotNull ConvertErrorRecorder errorRecorder);
72 |
73 | @Override
74 | public @NotNull String toString() {
75 | return "Property '" + path + "'";
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/properties/BeanProperty.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.properties;
2 |
3 | import ch.jalu.configme.beanmapper.DefaultMapper;
4 | import ch.jalu.configme.beanmapper.Mapper;
5 | import ch.jalu.configme.exception.ConfigMeException;
6 | import ch.jalu.configme.properties.types.BeanPropertyType;
7 | import ch.jalu.typeresolver.TypeInfo;
8 | import org.jetbrains.annotations.NotNull;
9 |
10 | /**
11 | * Property whose value is a bean class.
12 | *
13 | * @param the bean type
14 | */
15 | public class BeanProperty extends TypeBasedProperty {
16 |
17 | public BeanProperty(@NotNull String path, @NotNull Class beanType, @NotNull T defaultValue) {
18 | this(path, beanType, defaultValue, DefaultMapper.getInstance());
19 | }
20 |
21 | public BeanProperty(@NotNull String path, @NotNull Class beanType, @NotNull T defaultValue,
22 | @NotNull Mapper mapper) {
23 | super(path, BeanPropertyType.of(beanType, mapper), defaultValue);
24 | }
25 |
26 | public BeanProperty(@NotNull String path, @NotNull BeanPropertyType type, @NotNull T defaultValue) {
27 | super(path, type, defaultValue);
28 | }
29 |
30 | /**
31 | * Constructor. Allows to instantiate bean properties with generic types. Since it is hard to validate that
32 | * the default value is actually correct, it is recommended to extend this class with specific type parameters.
33 | *
34 | * @param path the path
35 | * @param beanType the bean type
36 | * @param defaultValue the default value
37 | * @param mapper the mapper to map with
38 | */
39 | protected BeanProperty(@NotNull String path, @NotNull TypeInfo beanType, @NotNull T defaultValue,
40 | @NotNull Mapper mapper) {
41 | super(path, new BeanPropertyType<>(beanType, mapper), defaultValue);
42 |
43 |
44 | Class> beanClass = beanType.toClass();
45 | if (beanClass == null) {
46 | throw new IllegalArgumentException("The bean type '" + beanType + "' cannot be converted to Class. "
47 | + "Use a constructor with a custom BeanPropertyType.");
48 | } else if (!beanClass.isInstance(defaultValue)) {
49 | throw new ConfigMeException(
50 | "Default value for path '" + path + "' does not match bean type '" + beanType + "'");
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/properties/BooleanProperty.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.properties;
2 |
3 | import ch.jalu.configme.properties.types.BooleanType;
4 | import org.jetbrains.annotations.NotNull;
5 |
6 | /**
7 | * Boolean property. This extension exists for convenience.
8 | */
9 | public class BooleanProperty extends TypeBasedProperty {
10 |
11 | public BooleanProperty(@NotNull String path, boolean defaultValue) {
12 | super(path, BooleanType.BOOLEAN, defaultValue);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/properties/DoubleProperty.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.properties;
2 |
3 | import ch.jalu.configme.properties.types.NumberType;
4 | import org.jetbrains.annotations.NotNull;
5 |
6 | /**
7 | * Double property. This extension exists for convenience.
8 | */
9 | public class DoubleProperty extends TypeBasedProperty {
10 |
11 | public DoubleProperty(@NotNull String path, double defaultValue) {
12 | super(path, NumberType.DOUBLE, defaultValue);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/properties/EnumProperty.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.properties;
2 |
3 | import ch.jalu.configme.properties.types.EnumPropertyType;
4 | import org.jetbrains.annotations.NotNull;
5 |
6 | /**
7 | * Enum property.
8 | *
9 | * @param the enum type
10 | */
11 | public class EnumProperty> extends TypeBasedProperty {
12 |
13 | public EnumProperty(@NotNull String path, @NotNull Class clazz, @NotNull E defaultValue) {
14 | super(path, EnumPropertyType.of(clazz), defaultValue);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/properties/EnumSetProperty.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.properties;
2 |
3 | import ch.jalu.configme.properties.types.EnumSetPropertyType;
4 | import org.jetbrains.annotations.NotNull;
5 |
6 | import java.util.Arrays;
7 | import java.util.EnumSet;
8 | import java.util.Set;
9 |
10 | /**
11 | * EnumSet property.
12 | *
13 | * @param the enum type
14 | */
15 | public class EnumSetProperty> extends SetProperty {
16 |
17 | public EnumSetProperty(@NotNull String path, @NotNull Class enumClass, @NotNull EnumSet defaultValue) {
18 | super(new EnumSetPropertyType(enumClass), path, defaultValue);
19 | }
20 |
21 | public EnumSetProperty(@NotNull String path, @NotNull Class enumClass, @NotNull E @NotNull... defaultValue) {
22 | super(new EnumSetPropertyType(enumClass), path, newEnumSet(enumClass, defaultValue));
23 | }
24 |
25 | private static > @NotNull Set newEnumSet(@NotNull Class enumClass,
26 | E @NotNull [] defaultValue) {
27 | EnumSet enumSet = EnumSet.noneOf(enumClass);
28 | enumSet.addAll(Arrays.asList(defaultValue));
29 | return enumSet;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/properties/FloatProperty.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.properties;
2 |
3 | import ch.jalu.configme.properties.types.NumberType;
4 | import org.jetbrains.annotations.NotNull;
5 |
6 | /**
7 | * Float property. This extension exists for convenience.
8 | */
9 | public class FloatProperty extends TypeBasedProperty {
10 |
11 | public FloatProperty(@NotNull String path, float defaultValue) {
12 | super(path, NumberType.FLOAT, defaultValue);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/properties/InlineArrayProperty.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.properties;
2 |
3 | import ch.jalu.configme.properties.types.InlineArrayPropertyType;
4 | import org.jetbrains.annotations.NotNull;
5 |
6 | /**
7 | * Array property which reads and stores its value as one String in which the elements
8 | * are separated by a delimiter.
9 | *
10 | * @param the array element type
11 | */
12 | public class InlineArrayProperty extends TypeBasedProperty {
13 |
14 | /**
15 | * Constructor.
16 | *
17 | * @param path the path of the property
18 | * @param inlineArrayType the inline array property type
19 | * @param defaultValue the default value of the property
20 | */
21 | public InlineArrayProperty(@NotNull String path,
22 | @NotNull InlineArrayPropertyType inlineArrayType,
23 | T @NotNull [] defaultValue) {
24 | super(path, inlineArrayType, defaultValue);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/properties/IntegerProperty.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.properties;
2 |
3 | import ch.jalu.configme.properties.types.NumberType;
4 | import org.jetbrains.annotations.NotNull;
5 |
6 | /**
7 | * Integer property. This extension exists for convenience.
8 | */
9 | public class IntegerProperty extends TypeBasedProperty {
10 |
11 | public IntegerProperty(@NotNull String path, int defaultValue) {
12 | super(path, NumberType.INTEGER, defaultValue);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/properties/LongProperty.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.properties;
2 |
3 | import ch.jalu.configme.properties.types.NumberType;
4 | import org.jetbrains.annotations.NotNull;
5 |
6 | /**
7 | * Long property. This extension exists for convenience.
8 | */
9 | public class LongProperty extends TypeBasedProperty {
10 |
11 | public LongProperty(@NotNull String path, long defaultValue) {
12 | super(path, NumberType.LONG, defaultValue);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/properties/LowercaseStringSetProperty.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.properties;
2 |
3 | import ch.jalu.configme.properties.types.StringType;
4 | import org.jetbrains.annotations.NotNull;
5 |
6 | import java.util.Arrays;
7 | import java.util.Collection;
8 | import java.util.LinkedHashSet;
9 | import java.util.Locale;
10 | import java.util.Set;
11 | import java.util.stream.Collectors;
12 | import java.util.stream.Stream;
13 |
14 | /**
15 | * Property whose value is a String set all in lowercase. The default value is immutable.
16 | * The encounter order of the default value and the constructed values is preserved.
17 | */
18 | public class LowercaseStringSetProperty extends SetProperty {
19 |
20 | /**
21 | * Constructor.
22 | *
23 | * @param path property path
24 | * @param defaultEntries entries in the Set that is the default value
25 | */
26 | public LowercaseStringSetProperty(@NotNull String path, @NotNull String @NotNull ... defaultEntries) {
27 | super(path, StringType.STRING_LOWER_CASE, toLowercaseLinkedHashSet(Arrays.stream(defaultEntries)));
28 | }
29 |
30 | /**
31 | * Constructor.
32 | *
33 | * @param path property path
34 | * @param defaultEntries entries in the Set that is the default value
35 | */
36 | public LowercaseStringSetProperty(@NotNull String path, @NotNull Collection defaultEntries) {
37 | super(path, StringType.STRING_LOWER_CASE, toLowercaseLinkedHashSet(defaultEntries.stream()));
38 | }
39 |
40 | protected static @NotNull Set toLowercaseLinkedHashSet(@NotNull Stream valuesStream) {
41 | return valuesStream
42 | .map(value -> value.toLowerCase(Locale.ROOT))
43 | .collect(Collectors.toCollection(LinkedHashSet::new));
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/properties/OptionalProperty.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.properties;
2 |
3 | import ch.jalu.configme.properties.convertresult.PropertyValue;
4 | import ch.jalu.configme.resource.PropertyReader;
5 | import org.jetbrains.annotations.NotNull;
6 | import org.jetbrains.annotations.Nullable;
7 |
8 | import java.util.Optional;
9 |
10 | /**
11 | * Property which may be empty.
12 | *
13 | * Wraps another property with an {@link Optional}: if a property is not present in the property resource,
14 | * {@link Optional#empty} is returned.
15 | *
16 | * @param the type of value
17 | */
18 | public class OptionalProperty implements Property> {
19 |
20 | private final Property baseProperty;
21 | private final Optional defaultValue;
22 |
23 | public OptionalProperty(@NotNull Property baseProperty) {
24 | this.baseProperty = baseProperty;
25 | this.defaultValue = Optional.empty();
26 | }
27 |
28 | public OptionalProperty(@NotNull Property baseProperty, @NotNull T defaultValue) {
29 | this.baseProperty = baseProperty;
30 | this.defaultValue = Optional.of(defaultValue);
31 | }
32 |
33 | @Override
34 | public @NotNull String getPath() {
35 | return baseProperty.getPath();
36 | }
37 |
38 | @Override
39 | public @NotNull PropertyValue> determineValue(@NotNull PropertyReader reader) {
40 | PropertyValue basePropertyValue = baseProperty.determineValue(reader);
41 | Optional value = basePropertyValue.isValidInResource()
42 | ? Optional.ofNullable(basePropertyValue.getValue())
43 | : Optional.empty();
44 |
45 | // Propagate the false "valid" property if the reader has a value at the base property's path
46 | // and the base property says it's invalid -> triggers a rewrite to get rid of the invalid value.
47 | boolean isWrongInResource = !basePropertyValue.isValidInResource() && reader.contains(baseProperty.getPath());
48 | return isWrongInResource
49 | ? PropertyValue.withValueRequiringRewrite(value)
50 | : PropertyValue.withValidValue(value);
51 | }
52 |
53 | @Override
54 | public @NotNull Optional getDefaultValue() {
55 | return defaultValue;
56 | }
57 |
58 | @Override
59 | public boolean isValidValue(@Nullable Optional value) {
60 | if (value == null) {
61 | return false;
62 | }
63 | return value.map(baseProperty::isValidValue).orElse(true);
64 | }
65 |
66 | @Override
67 | public @Nullable Object toExportValue(@NotNull Optional value) {
68 | return value.map(baseProperty::toExportValue).orElse(null);
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/properties/RegexProperty.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.properties;
2 |
3 | import ch.jalu.configme.SettingsManager;
4 | import ch.jalu.configme.properties.types.RegexType;
5 | import org.jetbrains.annotations.NotNull;
6 |
7 | import java.util.regex.Matcher;
8 | import java.util.regex.Pattern;
9 |
10 | /**
11 | * Property whose value is a regex pattern.
12 | */
13 | public class RegexProperty extends TypeBasedProperty {
14 |
15 | /**
16 | * Constructor.
17 | *
18 | * @param path the path of the property
19 | * @param defaultValue the default value of the property
20 | */
21 | public RegexProperty(@NotNull String path, @NotNull Pattern defaultValue) {
22 | super(path, RegexType.REGEX, defaultValue);
23 | }
24 |
25 | /**
26 | * Constructor.
27 | *
28 | * @param path the path of the property
29 | * @param defaultRegexValue the default value of the property
30 | */
31 | public RegexProperty(@NotNull String path, @NotNull String defaultRegexValue) {
32 | this(path, Pattern.compile(defaultRegexValue));
33 | }
34 |
35 | /**
36 | * Constructor that allows to specify a custom regex type.
37 | *
38 | * @param path the path of the property
39 | * @param type the property type
40 | * @param defaultValue the default value of the property
41 | */
42 | public RegexProperty(@NotNull String path, @NotNull RegexType type, @NotNull Pattern defaultValue) {
43 | super(path, type, defaultValue);
44 | }
45 |
46 | /**
47 | * Creates a new case-insensitive regex property: the patterns handled by this property are case-insensitive.
48 | *
49 | * @param path the path of the property
50 | * @param defaultRegexValue the default value of the property
51 | * @return new case-insensitive regex property
52 | */
53 | public static @NotNull RegexProperty caseInsensitive(@NotNull String path, @NotNull String defaultRegexValue) {
54 | return new RegexProperty(path, RegexType.REGEX_CASE_INSENSITIVE,
55 | Pattern.compile(defaultRegexValue, Pattern.CASE_INSENSITIVE));
56 | }
57 |
58 | /**
59 | * Convenience method to evaluate whether the pattern set for this property matches the provided {@code value}.
60 | *
61 | * @param value the value to check whether it conforms to the configured pattern
62 | * @param settingsManager settings manager with which the configured pattern is retrieved
63 | * @return true if the value matches the pattern, false otherwise
64 | */
65 | public boolean matches(@NotNull String value, @NotNull SettingsManager settingsManager) {
66 | Matcher matcher = settingsManager.getProperty(this).matcher(value);
67 | return matcher.matches();
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/properties/ShortProperty.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.properties;
2 |
3 | import ch.jalu.configme.properties.types.NumberType;
4 | import org.jetbrains.annotations.NotNull;
5 |
6 | /**
7 | * Short property. This extension exists for convenience.
8 | */
9 | public class ShortProperty extends TypeBasedProperty {
10 |
11 | public ShortProperty(@NotNull String path, short defaultValue) {
12 | super(path, NumberType.SHORT, defaultValue);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/properties/StringListProperty.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.properties;
2 |
3 | import ch.jalu.configme.properties.types.StringType;
4 | import org.jetbrains.annotations.NotNull;
5 |
6 | import java.util.List;
7 |
8 | /**
9 | * String list property. The lists are immutable.
10 | */
11 | public class StringListProperty extends ListProperty {
12 |
13 | public StringListProperty(@NotNull String path, String @NotNull ... defaultValue) {
14 | super(path, StringType.STRING, defaultValue);
15 | }
16 |
17 | public StringListProperty(@NotNull String path, @NotNull List defaultValue) {
18 | super(path, StringType.STRING, defaultValue);
19 | }
20 |
21 | @Override
22 | public @NotNull Object toExportValue(@NotNull List value) {
23 | return value;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/properties/StringProperty.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.properties;
2 |
3 | import ch.jalu.configme.properties.types.StringType;
4 | import org.jetbrains.annotations.NotNull;
5 |
6 | /**
7 | * String property. This extension exists for convenience.
8 | */
9 | public class StringProperty extends TypeBasedProperty {
10 |
11 | public StringProperty(@NotNull String path, @NotNull String defaultValue) {
12 | super(path, StringType.STRING, defaultValue);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/properties/StringSetProperty.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.properties;
2 |
3 | import ch.jalu.configme.properties.types.StringType;
4 | import org.jetbrains.annotations.NotNull;
5 |
6 | import java.util.Set;
7 |
8 | /**
9 | * String set property. The default value is immutable. The encounter order of the default value and
10 | * the constructed values is preserved.
11 | */
12 | public class StringSetProperty extends SetProperty {
13 |
14 | /**
15 | * Constructor.
16 | *
17 | * @param path the path of the property
18 | * @param defaultValue the values that make up the entries of the default set
19 | */
20 | public StringSetProperty(@NotNull String path, @NotNull String @NotNull... defaultValue) {
21 | super(path, StringType.STRING, defaultValue);
22 | }
23 |
24 | /**
25 | * Constructor.
26 | *
27 | * @param path the path of the property
28 | * @param defaultValue the values that make up the entries of the default set
29 | */
30 | public StringSetProperty(@NotNull String path, @NotNull Set defaultValue) {
31 | super(path, StringType.STRING, defaultValue);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/properties/TypeBasedProperty.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.properties;
2 |
3 | import ch.jalu.configme.properties.convertresult.ConvertErrorRecorder;
4 | import ch.jalu.configme.properties.types.PropertyType;
5 | import ch.jalu.configme.resource.PropertyReader;
6 | import org.jetbrains.annotations.NotNull;
7 |
8 | import org.jetbrains.annotations.Nullable;
9 | import java.util.Objects;
10 |
11 | /**
12 | * Property implementation which relies on a {@link PropertyType}.
13 | *
14 | * @param type of property value
15 | */
16 | public class TypeBasedProperty extends BaseProperty {
17 |
18 | private final PropertyType type;
19 |
20 | /**
21 | * Constructor.
22 | *
23 | * @param path the path of the property
24 | * @param type the property type
25 | * @param defaultValue the default value of the property
26 | */
27 | public TypeBasedProperty(@NotNull String path, @NotNull PropertyType type, @NotNull T defaultValue) {
28 | super(path, defaultValue);
29 | Objects.requireNonNull(type, "type");
30 | this.type = type;
31 | }
32 |
33 | @Override
34 | protected @Nullable T getFromReader(@NotNull PropertyReader reader, @NotNull ConvertErrorRecorder errorRecorder) {
35 | return type.convert(reader.getObject(getPath()), errorRecorder);
36 | }
37 |
38 | @Override
39 | public @Nullable Object toExportValue(@NotNull T value) {
40 | return type.toExportValue(value);
41 | }
42 |
43 | /**
44 | * @return the property type this property makes use of
45 | */
46 | public @NotNull PropertyType getType() {
47 | return type;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/properties/builder/PropertyBuilderUtils.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.properties.builder;
2 |
3 | import org.jetbrains.annotations.Nullable;
4 |
5 | /**
6 | * Utilities for property builders.
7 | */
8 | final class PropertyBuilderUtils {
9 |
10 | /**
11 | * Method name to reference in an error message, for property builders that have an
12 | * array, collection or map as their value.
13 | */
14 | static final String ADD_TO_DEFAULT_VALUE_METHOD = "addToDefaultValue";
15 |
16 | private PropertyBuilderUtils() {
17 | }
18 |
19 | /**
20 | * Verifies that the given path is not null, throwing an exception if it is.
21 | *
22 | * @param path the path to check
23 | */
24 | static void requireNonNullPath(@Nullable String path) {
25 | if (path == null) {
26 | throw new IllegalStateException("The path of the property must be defined");
27 | }
28 | }
29 |
30 | /**
31 | * Throws an exception referring to the method {@code addToDefaultValue} if the provided parameter indicates that
32 | * the default value collection/map is not empty.
33 | *
34 | * @param isEmpty whether the default value (collection/map) is currently empty
35 | */
36 | static void verifyDefaultValueIsEmpty(boolean isEmpty) {
37 | if (!isEmpty) {
38 | throw new IllegalStateException("Default values have already been defined! Use "
39 | + ADD_TO_DEFAULT_VALUE_METHOD + " to add entries individually");
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/properties/convertresult/ConvertErrorRecorder.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.properties.convertresult;
2 |
3 | import ch.jalu.configme.resource.PropertyReader;
4 | import org.jetbrains.annotations.NotNull;
5 |
6 | /**
7 | * Records errors during the conversion of a property to its Java value.
8 | *
9 | * This object is passed around during conversion to determine whether an error has occurred during the conversion that
10 | * should trigger a rewrite. It is not necessary to register an error with this class if the return value of the
11 | * conversion implies that the representation in the resource is wrong altogether.
12 | * Instead, errors are typically registered with this recorder when an object can be created, but there is some
13 | * error in the representation that should be corrected (e.g. a value is missing but there is a sensible fallback).
14 | *
15 | * @see ch.jalu.configme.properties.BaseProperty#determineValue(PropertyReader)
16 | */
17 | public class ConvertErrorRecorder {
18 |
19 | private boolean hasError;
20 |
21 | /**
22 | * Registers that some error occurred during the conversion of the value. See class javadoc: no need to register
23 | * an error if the return value of the conversion implies there is an issue (such as returning null).
24 | *
25 | * @param reason the reason (not used in this implementation but may be extended for debugging)
26 | */
27 | public void setHasError(@NotNull String reason) {
28 | hasError = true;
29 | }
30 |
31 | /**
32 | * Returns whether the value that was returned from the property or property type was fully valid and
33 | * therefore doesn't need a rewrite. Even if a value is returned, this method may return {@code false} in case
34 | * a recoverable error was detected. This method may return {@code true} if the return value of the conversion is
35 | * null or otherwise indicates that there is no proper representation of it in the property resource.
36 | *
37 | * @return true if no error was registered, false otherwise (see class Javadoc for semantics)
38 | */
39 | public boolean isFullyValid() {
40 | return !hasError;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/properties/types/ArrayPropertyType.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.properties.types;
2 |
3 | import ch.jalu.configme.internal.ConversionUtils;
4 | import ch.jalu.configme.properties.convertresult.ConvertErrorRecorder;
5 | import org.jetbrains.annotations.NotNull;
6 | import org.jetbrains.annotations.Nullable;
7 |
8 | import java.util.Arrays;
9 | import java.util.Collection;
10 | import java.util.List;
11 | import java.util.Objects;
12 | import java.util.function.IntFunction;
13 | import java.util.stream.Collectors;
14 |
15 | /**
16 | * Property type for arrays: wraps another property type, which handles individual array elements, into an array type.
17 | *
18 | * This property type never produces arrays that have any {@code null} elements. If an entry cannot be converted by the
19 | * entry type (the property type that converts individual array elements), it is skipped in the result.
20 | *
21 | * @param the type of the array's elements
22 | */
23 | public class ArrayPropertyType implements PropertyType {
24 |
25 | private final PropertyType entryType;
26 | private final IntFunction arrayProducer;
27 |
28 | /**
29 | * Constructor.
30 | *
31 | * Note that many property types provided by ConfigMe have a method {@code arrayType()},
32 | * e.g. {@link NumberType#arrayType()}, to create an array equivalent of the base type.
33 | *
34 | * @param entryType the type of the array's elements
35 | * @param arrayProducer function to create an array of the right type with the provided size
36 | */
37 | public ArrayPropertyType(@NotNull PropertyType entryType, @NotNull IntFunction arrayProducer) {
38 | Objects.requireNonNull(entryType, "entryType");
39 | Objects.requireNonNull(arrayProducer, "arrayProducer");
40 | this.entryType = entryType;
41 | this.arrayProducer = arrayProducer;
42 | }
43 |
44 | @Override
45 | public T @Nullable [] convert(@Nullable Object object, @NotNull ConvertErrorRecorder errorRecorder) {
46 | if (object instanceof Collection>) {
47 | Collection> coll = (Collection>) object;
48 | return coll.stream()
49 | .map(elem -> ConversionUtils.convertOrLogError(elem, entryType, errorRecorder))
50 | .filter(Objects::nonNull)
51 | .toArray(arrayProducer);
52 | }
53 | return null;
54 | }
55 |
56 | @Override
57 | public @NotNull List> toExportValue(T @NotNull [] value) {
58 | return Arrays.stream(value)
59 | .map(entryType::toExportValue)
60 | .collect(Collectors.toList());
61 | }
62 |
63 | public final @NotNull PropertyType getEntryType() {
64 | return entryType;
65 | }
66 |
67 | /**
68 | * @return function to create an array with the given capacity
69 | */
70 | public final @NotNull IntFunction getArrayProducer() {
71 | return arrayProducer;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/properties/types/BeanPropertyType.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.properties.types;
2 |
3 | import ch.jalu.configme.beanmapper.DefaultMapper;
4 | import ch.jalu.configme.beanmapper.Mapper;
5 | import ch.jalu.configme.properties.convertresult.ConvertErrorRecorder;
6 | import ch.jalu.typeresolver.TypeInfo;
7 | import org.jetbrains.annotations.NotNull;
8 | import org.jetbrains.annotations.Nullable;
9 |
10 | /**
11 | * Property type that maps values to a specific bean class.
12 | *
13 | * @param the bean type
14 | */
15 | public class BeanPropertyType implements PropertyType {
16 |
17 | private final TypeInfo beanType;
18 | private final Mapper mapper;
19 |
20 | public BeanPropertyType(@NotNull TypeInfo beanType, @NotNull Mapper mapper) {
21 | this.beanType = beanType;
22 | this.mapper = mapper;
23 | }
24 |
25 | public static @NotNull BeanPropertyType of(@NotNull Class type, @NotNull Mapper mapper) {
26 | return new BeanPropertyType<>(new TypeInfo(type), mapper);
27 | }
28 |
29 | public static @NotNull BeanPropertyType of(@NotNull Class type) {
30 | return of(type, DefaultMapper.getInstance());
31 | }
32 |
33 | @Override
34 | @SuppressWarnings("unchecked")
35 | public B convert(@Nullable Object object, @NotNull ConvertErrorRecorder errorRecorder) {
36 | return (B) mapper.convertToBean(object, beanType, errorRecorder);
37 | }
38 |
39 | @Override
40 | public @Nullable Object toExportValue(@NotNull B value) {
41 | return mapper.toExportValue(value);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/properties/types/BooleanType.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.properties.types;
2 |
3 | import ch.jalu.configme.properties.convertresult.ConvertErrorRecorder;
4 | import ch.jalu.typeresolver.TypeInfo;
5 | import ch.jalu.typeresolver.primitives.PrimitiveType;
6 | import org.jetbrains.annotations.NotNull;
7 | import org.jetbrains.annotations.Nullable;
8 |
9 | /**
10 | * Property type and mapper leaf type for boolean values.
11 | */
12 | public class BooleanType extends PropertyAndLeafType {
13 |
14 | /** Instance of this class. Named {@code BOOLEAN} rather than {@code INSTANCE} so it can be statically imported. */
15 | public static final BooleanType BOOLEAN = new BooleanType();
16 |
17 | /**
18 | * Constructor. Use {@link BooleanType#BOOLEAN} for the standard behavior.
19 | */
20 | protected BooleanType() {
21 | super(Boolean.class);
22 | }
23 |
24 | @Override
25 | public @Nullable Boolean convert(@Nullable Object object, @NotNull ConvertErrorRecorder errorRecorder) {
26 | if (object instanceof Boolean) {
27 | return (Boolean) object;
28 | } else if (object instanceof String) {
29 | return convertFromString((String) object);
30 | }
31 | return null;
32 | }
33 |
34 | @Override
35 | public @NotNull Boolean toExportValue(@NotNull Boolean value) {
36 | return value;
37 | }
38 |
39 | @Override
40 | public boolean canConvertToType(@NotNull TypeInfo typeInformation) {
41 | Class> requiredClass = PrimitiveType.toReferenceType(typeInformation.toClass());
42 | return requiredClass != null && requiredClass.isAssignableFrom(Boolean.class);
43 | }
44 |
45 | /**
46 | * Converts the String value to its boolean value, if applicable.
47 | *
48 | * @param value the value to convert
49 | * @return boolean value represented by the string, or null if not applicable
50 | */
51 | protected @Nullable Boolean convertFromString(@NotNull String value) {
52 | // Note: Explicitly check for true/false because Boolean#parseBoolean returns false for
53 | // any value it doesn't recognize
54 | if ("true".equalsIgnoreCase(value)) {
55 | return true;
56 | } else if ("false".equalsIgnoreCase(value)) {
57 | return false;
58 | }
59 | return null;
60 | }
61 |
62 | /**
63 | * @return array property type whose elements are managed by {@code this} boolean type
64 | */
65 | public @NotNull ArrayPropertyType arrayType() {
66 | return new ArrayPropertyType<>(this, Boolean[]::new);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/properties/types/EnumPropertyType.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.properties.types;
2 |
3 | import ch.jalu.configme.internal.ConversionUtils;
4 | import ch.jalu.configme.properties.convertresult.ConvertErrorRecorder;
5 | import ch.jalu.typeresolver.EnumUtils;
6 | import org.jetbrains.annotations.NotNull;
7 | import org.jetbrains.annotations.Nullable;
8 |
9 | /**
10 | * Property type for an enum type.
11 | *
12 | * @param the enum type
13 | */
14 | public class EnumPropertyType> implements PropertyType {
15 |
16 | private final Class enumType;
17 |
18 | /**
19 | * Constructor. You can also create instances with {@link EnumPropertyType#of}.
20 | *
21 | * @param enumType the enum type this type should convert to
22 | */
23 | public EnumPropertyType(@NotNull Class enumType) {
24 | this.enumType = enumType;
25 | }
26 |
27 | public static > @NotNull EnumPropertyType of(@NotNull Class type) {
28 | return new EnumPropertyType<>(type);
29 | }
30 |
31 | @Override
32 | @SuppressWarnings("unchecked")
33 | public @Nullable E convert(@Nullable Object object, @NotNull ConvertErrorRecorder errorRecorder) {
34 | if (object instanceof String) {
35 | return EnumUtils.tryValueOfCaseInsensitive(enumType, (String) object).orElse(null);
36 | } else if (enumType.isInstance(object)) {
37 | return (E) object;
38 | }
39 | return null;
40 | }
41 |
42 | @Override
43 | public @NotNull String toExportValue(@NotNull E value) {
44 | return value.name();
45 | }
46 |
47 | public final @NotNull Class getEnumClass() {
48 | return enumType;
49 | }
50 |
51 | /**
52 | * @return array property type whose elements are managed by {@code this} enum type
53 | */
54 | public @NotNull ArrayPropertyType arrayType() {
55 | return new ArrayPropertyType<>(this, size -> ConversionUtils.createArrayForReferenceType(enumType, size));
56 | }
57 |
58 | /**
59 | * Creates an inline array property type with the given separator.
60 | * See {@link InlineArrayPropertyType} for more details.
61 | *
62 | * @param separator the sequence that acts as separator for multiple entries
63 | * @return inline array type with {@code this} type and the given separator
64 | */
65 | public @NotNull InlineArrayPropertyType inlineArrayType(@NotNull String separator) {
66 | return new InlineArrayPropertyType<>(this, separator, true,
67 | size -> ConversionUtils.createArrayForReferenceType(enumType, size));
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/properties/types/EnumSetPropertyType.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.properties.types;
2 |
3 | import org.jetbrains.annotations.NotNull;
4 |
5 | import java.util.EnumSet;
6 | import java.util.stream.Collector;
7 | import java.util.stream.Collectors;
8 |
9 | /**
10 | * Property type that manages an enum set.
11 | *
12 | * @param the enum type of the entries
13 | */
14 | public class EnumSetPropertyType> extends CollectionPropertyType> {
15 |
16 | /**
17 | * Constructor.
18 | *
19 | * @param enumClass the enum class the entries should have
20 | */
21 | public EnumSetPropertyType(@NotNull Class enumClass) {
22 | this(EnumPropertyType.of(enumClass));
23 | }
24 |
25 | /**
26 | * Constructor.
27 | *
28 | * @param entryType enum type of the entries
29 | */
30 | public EnumSetPropertyType(@NotNull EnumPropertyType entryType) {
31 | super(entryType);
32 | }
33 |
34 | @Override
35 | public @NotNull EnumPropertyType getEntryType() {
36 | return (EnumPropertyType) super.getEntryType();
37 | }
38 |
39 | public @NotNull Class getEnumClass() {
40 | return getEntryType().getEnumClass();
41 | }
42 |
43 | @Override
44 | protected @NotNull Collector> resultCollector() {
45 | return Collectors.toCollection(() -> EnumSet.noneOf(getEnumClass()));
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/properties/types/ListPropertyType.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.properties.types;
2 |
3 | import org.jetbrains.annotations.NotNull;
4 |
5 | import java.util.ArrayList;
6 | import java.util.List;
7 | import java.util.stream.Collector;
8 | import java.util.stream.Collectors;
9 |
10 | /**
11 | * Property type for lists.
12 | *
13 | * @param the type of the elements in the list
14 | */
15 | public class ListPropertyType extends CollectionPropertyType> {
16 |
17 | public ListPropertyType(@NotNull PropertyType entryType) {
18 | super(entryType);
19 | }
20 |
21 | @Override
22 | protected @NotNull Collector> resultCollector() {
23 | // Note: Collectors#toList creates an ArrayList, but the Javadoc makes no guarantees about what type of List
24 | // will actually be returned, so we'll explicitly use an ArrayList here.
25 | return Collectors.toCollection(ArrayList::new);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/properties/types/PropertyAndLeafType.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.properties.types;
2 |
3 | import ch.jalu.configme.beanmapper.leafvaluehandler.MapperLeafType;
4 | import ch.jalu.configme.properties.convertresult.ConvertErrorRecorder;
5 | import ch.jalu.typeresolver.TypeInfo;
6 | import org.jetbrains.annotations.NotNull;
7 | import org.jetbrains.annotations.Nullable;
8 |
9 | /**
10 | * Abstract class to implement {@link PropertyType} and {@link MapperLeafType} within the same class.
11 | *
12 | * Both implemented interfaces are independent of each other, but for many basic types, we want to make use of the same
13 | * conversion logic and behavior. This class facilitates such implementations. This class should not be used for
14 | * types where the conversion logic is not suitable as a property type and as a mapper leaf type—for
15 | * instance, {@link EnumPropertyType} is a property type implementation for properties where the enum class is
16 | * specifically defined, whereas {@link ch.jalu.configme.beanmapper.leafvaluehandler.EnumLeafType EnumLeafType} is the
17 | * equivalent mapper leaf type which generically handles all enums.
18 | *
19 | * @param the type this instance produces
20 | */
21 | public abstract class PropertyAndLeafType implements PropertyType, MapperLeafType {
22 |
23 | private final Class clazz;
24 |
25 | /**
26 | * Constructor.
27 | *
28 | * @param clazz the class this type implementation produces
29 | */
30 | public PropertyAndLeafType(@NotNull Class clazz) {
31 | this.clazz = clazz;
32 | }
33 |
34 | @Override
35 | public @Nullable Object convert(@Nullable Object value, @NotNull TypeInfo targetType,
36 | @NotNull ConvertErrorRecorder errorRecorder) {
37 | if (canConvertToType(targetType)) {
38 | return convert(value, errorRecorder);
39 | }
40 | return null;
41 | }
42 |
43 | @Override
44 | @SuppressWarnings("unchecked")
45 | public @Nullable Object toExportValueIfApplicable(@Nullable Object value) {
46 | if (clazz.isInstance(value)) {
47 | return toExportValue((T) value);
48 | }
49 | return null;
50 | }
51 |
52 | /**
53 | * Specifies whether this object can convert to the given type. Used by
54 | * {@link #convert(Object, TypeInfo, ConvertErrorRecorder)}.
55 | *
56 | * @param type the target type
57 | * @return true if this object can convert to the given type, false otherwise
58 | */
59 | protected boolean canConvertToType(@NotNull TypeInfo type) {
60 | return type.isAssignableFrom(clazz);
61 | }
62 |
63 | /**
64 | * @return the class of the values this type converts to
65 | */
66 | public final @NotNull Class getType() {
67 | return clazz;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/properties/types/PropertyType.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.properties.types;
2 |
3 | import ch.jalu.configme.properties.convertresult.ConvertErrorRecorder;
4 |
5 | import org.jetbrains.annotations.NotNull;
6 | import org.jetbrains.annotations.Nullable;
7 |
8 | /**
9 | * Property type: provides methods for converting between property resource and a defined type
10 | * and allows to be used in generic structures such as an array property or map property.
11 | *
12 | * @param type of the values the property type handles
13 | */
14 | public interface PropertyType {
15 |
16 | /**
17 | * Converts the given object (typically read from a property resource) to the given type, if possible.
18 | * Returns null otherwise.
19 | *
20 | * @param object the object to convert
21 | * @param errorRecorder error recorder to register errors even if a valid value is returned
22 | * @return the converted value, or null
23 | */
24 | @Nullable T convert(@Nullable Object object, @NotNull ConvertErrorRecorder errorRecorder);
25 |
26 | /**
27 | * Converts the given value to its export value. (Converts in the opposite way of {@link #convert}.)
28 | *
29 | * @param value the value to convert
30 | * @return the value to use in the property export
31 | */
32 | @Nullable Object toExportValue(@NotNull T value);
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/properties/types/RegexType.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.properties.types;
2 |
3 | import ch.jalu.configme.properties.convertresult.ConvertErrorRecorder;
4 | import org.jetbrains.annotations.NotNull;
5 | import org.jetbrains.annotations.Nullable;
6 |
7 | import java.util.regex.Pattern;
8 | import java.util.regex.PatternSyntaxException;
9 |
10 | /**
11 | * Property type and mapper leaf type for regex.
12 | */
13 | public class RegexType extends PropertyAndLeafType {
14 |
15 | /** Default regex type. */
16 | public static final RegexType REGEX = new RegexType();
17 |
18 | /** Case-insensitive regex type. */
19 | public static final RegexType REGEX_CASE_INSENSITIVE = new RegexType() {
20 | @Override
21 | protected @NotNull Pattern compileToPattern(@NotNull String regex) {
22 | return Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
23 | }
24 | };
25 |
26 | /**
27 | * Constructor. Use {@link RegexType#REGEX} for the standard behavior.
28 | */
29 | protected RegexType() {
30 | super(Pattern.class);
31 | }
32 |
33 | @Override
34 | public @Nullable Pattern convert(@Nullable Object object, @NotNull ConvertErrorRecorder errorRecorder) {
35 | if (object instanceof String) {
36 | String regex = (String) object;
37 | try {
38 | return compileToPattern(regex);
39 | } catch (PatternSyntaxException ignored) {
40 | }
41 | }
42 | return null;
43 | }
44 |
45 | @Override
46 | public @Nullable Object toExportValue(@NotNull Pattern value) {
47 | return value.pattern();
48 | }
49 |
50 | /**
51 | * Compiles the given string to a pattern object.
52 | *
53 | * @param regex the string to compile
54 | * @return the pattern object
55 | */
56 | protected @NotNull Pattern compileToPattern(@NotNull String regex) {
57 | return Pattern.compile(regex);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/properties/types/SetPropertyType.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.properties.types;
2 |
3 | import org.jetbrains.annotations.NotNull;
4 |
5 | import java.util.LinkedHashSet;
6 | import java.util.Set;
7 | import java.util.stream.Collector;
8 | import java.util.stream.Collectors;
9 |
10 | /**
11 | * Property type for sets. The sets produced by this type are {@link LinkedHashSet},
12 | * ensuring that insertion order is preserved.
13 | *
14 | * @param the type of the elements in the set
15 | */
16 | public class SetPropertyType extends CollectionPropertyType> {
17 |
18 | public SetPropertyType(@NotNull PropertyType entryType) {
19 | super(entryType);
20 | }
21 |
22 | @Override
23 | protected @NotNull Collector> resultCollector() {
24 | return Collectors.toCollection(LinkedHashSet::new);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/properties/types/StringType.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.properties.types;
2 |
3 | import ch.jalu.configme.properties.convertresult.ConvertErrorRecorder;
4 | import org.jetbrains.annotations.NotNull;
5 | import org.jetbrains.annotations.Nullable;
6 |
7 | import java.util.Locale;
8 |
9 | /**
10 | * Property type and mapper leaf type for strings.
11 | */
12 | public class StringType extends PropertyAndLeafType {
13 |
14 | /** Default string type. */
15 | public static final StringType STRING = new StringType();
16 |
17 | /** Lowercase string type. */
18 | public static final StringType STRING_LOWER_CASE = new StringType() {
19 | @Override
20 | protected @NotNull String transformToString(@NotNull Object object) {
21 | return object.toString().toLowerCase(Locale.ROOT);
22 | }
23 | };
24 |
25 | /**
26 | * Constructor.
27 | */
28 | protected StringType() {
29 | super(String.class);
30 | }
31 |
32 | @Override
33 | public @Nullable String convert(@Nullable Object object, @NotNull ConvertErrorRecorder errorRecorder) {
34 | return object == null ? null : transformToString(object);
35 | }
36 |
37 | @Override
38 | public @NotNull String toExportValue(@NotNull String value) {
39 | return value;
40 | }
41 |
42 | /**
43 | * Converts the given object to a string.
44 | *
45 | * @param object the object to convert
46 | * @return the converted object
47 | */
48 | protected @NotNull String transformToString(@NotNull Object object) {
49 | return object.toString();
50 | }
51 |
52 | /**
53 | * @return array property type whose elements are managed by {@code this} String type
54 | */
55 | public @NotNull ArrayPropertyType arrayType() {
56 | return new ArrayPropertyType<>(this, String[]::new);
57 | }
58 |
59 | /**
60 | * Creates an inline array property type with the given separator.
61 | * See {@link InlineArrayPropertyType} for more details.
62 | *
63 | * @param separator the sequence that acts as separator for multiple entries
64 | * @return inline array type with {@code this} type and the given separator
65 | */
66 | public @NotNull InlineArrayPropertyType inlineArrayType(@NotNull String separator) {
67 | return new InlineArrayPropertyType<>(this, separator, false, String[]::new);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/resource/PropertyResource.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.resource;
2 |
3 | import ch.jalu.configme.configurationdata.ConfigurationData;
4 | import org.jetbrains.annotations.NotNull;
5 |
6 | /**
7 | * Represents a medium (typically a file on disk) from which property values should be built and allows to
8 | * write back to it.
9 | */
10 | public interface PropertyResource {
11 |
12 | /**
13 | * Creates a reader to access the values in the medium (typically a file).
14 | *
15 | * The reader is discarded after its use and so is not required to refresh itself.
16 | *
17 | * @return reader providing values in the medium (e.g. file)
18 | */
19 | @NotNull PropertyReader createReader();
20 |
21 | /**
22 | * Exports the provided configuration data to the medium (typically a file).
23 | *
24 | * @param configurationData the configuration data to export
25 | */
26 | void exportProperties(@NotNull ConfigurationData configurationData);
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/resource/yaml/SnakeYamlNodeBuilder.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.resource.yaml;
2 |
3 | import ch.jalu.configme.configurationdata.ConfigurationData;
4 | import org.jetbrains.annotations.NotNull;
5 | import org.yaml.snakeyaml.comments.CommentLine;
6 | import org.yaml.snakeyaml.nodes.Node;
7 |
8 | import java.util.stream.Stream;
9 |
10 | /**
11 | * Creates SnakeYAML nodes for values and comments.
12 | */
13 | public interface SnakeYamlNodeBuilder {
14 |
15 | /**
16 | * Creates a SnakeYAML node representing the given value.
17 | *
18 | * @param value the value to create the node for (export value of a property)
19 | * @param path the path of the property whose value is exported
20 | * @param configurationData configuration data (for the retrieval of comments)
21 | * @param numberOfNewLines number of new lines before the property, to add as new lines to the node
22 | * @return SnakeYAML node of the appropriate type for the value, including comments
23 | */
24 | @NotNull Node createYamlNode(@NotNull Object value, @NotNull String path,
25 | @NotNull ConfigurationData configurationData, int numberOfNewLines);
26 |
27 | /**
28 | * Creates a SnakeYAML string node for a key value (e.g. object property, or map key).
29 | *
30 | * @param key the key to wrap into a node
31 | * @return a node representing the key value
32 | */
33 | @NotNull Node createKeyNode(@NotNull String key);
34 |
35 | /**
36 | * Creates SnakeYAML {@link CommentLine} objects to represent the given comment. If the comment is equal to the
37 | * new line character {@code \n}, one comment line representing a blank line is returned. Otherwise, a comment line
38 | * is created for each line (the text is split by {@code \n}).
39 | *
40 | * @param comment the comment to represent as CommentLine
41 | * @return stream with comment line objects representing the given comment
42 | */
43 | @NotNull Stream createCommentLines(@NotNull String comment);
44 |
45 | /**
46 | * Transfers the comments from the value node to the key node. Logically, comments are associated with values,
47 | * but we do not want the comments to appear between the key and the value in the YAML output. Therefore, this
48 | * method is called before producing YAML as to move the comments from the value to the key node.
49 | *
50 | * @implNote Only considers {@link Node#getBlockComments() block comments} on the nodes because it's the only type
51 | * of comment that this builder sets. Any block comments on the key node are overwritten.
52 | *
53 | * @param valueNode the value node to remove the comments from
54 | * @param keyNode the key node to set the comments to
55 | */
56 | void transferComments(@NotNull Node valueNode, @NotNull Node keyNode);
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/resource/yaml/SnakeYamlNodeContainer.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.resource.yaml;
2 |
3 | import org.jetbrains.annotations.NotNull;
4 | import org.yaml.snakeyaml.nodes.Node;
5 |
6 | import java.util.List;
7 | import java.util.function.Supplier;
8 |
9 | /**
10 | * Container that keeps SnakeYAML node objects in hierarchical order. Leaf values are SnakeYAML nodes, while parent
11 | * values are containers that can be converted to SnakeYAML nodes representing all enclosed values.
12 | */
13 | public interface SnakeYamlNodeContainer {
14 |
15 | /**
16 | * Returns the existing container for the given name, or creates one and registers the comments as returned by the
17 | * supplier. An exception is thrown if a value (SnakeYAML node) was saved under the given name.
18 | *
19 | * @param name the path name to get
20 | * @param commentsSupplier supplier with comments to set if the container has to be created
21 | * @return container for the given path name
22 | */
23 | @NotNull SnakeYamlNodeContainer getOrCreateChildContainer(@NotNull String name,
24 | @NotNull Supplier> commentsSupplier);
25 |
26 | /**
27 | * Returns the SnakeYAML node at the root path (empty string). Used as root of the YAML document when the
28 | * configuration only has one property at root path. Throws an exception if no value was stored for the root path.
29 | *
30 | * @return internal root node
31 | */
32 | @NotNull Node getRootValueNode();
33 |
34 | /**
35 | * Saves the given node under the given name (= path element). Throws an exception if a value is already associated
36 | * with the given name.
37 | *
38 | * @param name the name to save the value under
39 | * @param node the node to save
40 | */
41 | void putNode(@NotNull String name, @NotNull Node node);
42 |
43 | /**
44 | * Converts this container and its sub-containers, recursively, to a SnakeYAML node that represents
45 | * all SnakeYAML nodes held by the containers.
46 | *
47 | * @param nodeBuilder node builder to create nodes with
48 | * @return this container's values as SnakeYAML node
49 | */
50 | @NotNull Node convertToNode(@NotNull SnakeYamlNodeBuilder nodeBuilder);
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/utils/FileUtils.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.utils;
2 |
3 | import ch.jalu.configme.exception.ConfigMeException;
4 | import org.jetbrains.annotations.NotNull;
5 |
6 | import java.io.IOException;
7 | import java.nio.file.Files;
8 | import java.nio.file.Path;
9 |
10 | /**
11 | * Class with file utilities.
12 | */
13 | public final class FileUtils {
14 |
15 | private FileUtils() {
16 | }
17 |
18 | /**
19 | * Attempts to create the given Path (as file) if it doesn't exist. If creating the file
20 | * is unsuccessful, an exception is thrown. If the given path exists but is not a file,
21 | * an exception is thrown, too.
22 | *
23 | * @param file the file to create if it doesn't exist
24 | */
25 | public static void createFileIfNotExists(@NotNull Path file) {
26 | if (Files.exists(file)) {
27 | if (!Files.isRegularFile(file)) {
28 | throw new ConfigMeException("Expected file but '" + file + "' is not a file");
29 | }
30 | } else {
31 | Path parent = file.getParent();
32 | if (!Files.exists(parent) || !Files.isDirectory(parent)) {
33 | try {
34 | Files.createDirectories(parent);
35 | } catch (IOException e) {
36 | throw new ConfigMeException("Failed to create parent folders for '" + file + "'", e);
37 | }
38 | }
39 | try {
40 | Files.createFile(file);
41 | } catch (IOException e) {
42 | throw new ConfigMeException("Failed to create file '" + file + "'", e);
43 | }
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/ch/jalu/configme/utils/MigrationUtils.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme.utils;
2 |
3 | import ch.jalu.configme.configurationdata.ConfigurationData;
4 | import ch.jalu.configme.properties.Property;
5 | import ch.jalu.configme.properties.convertresult.PropertyValue;
6 | import ch.jalu.configme.resource.PropertyReader;
7 | import org.jetbrains.annotations.NotNull;
8 |
9 | /**
10 | * Migration utils.
11 | */
12 | public final class MigrationUtils {
13 |
14 | private MigrationUtils() {
15 | }
16 |
17 | /**
18 | * Utility method: moves the value of an old property to a new property. This is only done if there is no value for
19 | * the new property in the configuration file and if there is one for the old property. Returns true if a value is
20 | * present at the old property path.
21 | *
22 | * @param oldProperty the old property (create a temporary {@link Property} object with the path)
23 | * @param newProperty the new property to move the value to
24 | * @param reader the property reader to read the configuration file from
25 | * @param configurationData configuration data to update a property's value
26 | * @param the type of the property
27 | * @return true if the old path exists in the configuration file, false otherwise
28 | */
29 | public static boolean moveProperty(@NotNull Property oldProperty,
30 | @NotNull Property newProperty,
31 | @NotNull PropertyReader reader,
32 | @NotNull ConfigurationData configurationData) {
33 | if (reader.contains(oldProperty.getPath())) {
34 | if (!reader.contains(newProperty.getPath())) {
35 | PropertyValue value = oldProperty.determineValue(reader);
36 | configurationData.setValue(newProperty, value.getValue());
37 | }
38 | return true;
39 | }
40 | return false;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/test/java/ch/jalu/configme/BeanDemoTest.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme;
2 |
3 | import ch.jalu.configme.demo.beans.BeanPropertiesDemo;
4 | import org.junit.jupiter.api.Test;
5 |
6 | import java.io.IOException;
7 | import java.nio.file.Files;
8 | import java.nio.file.Path;
9 |
10 | import static org.hamcrest.MatcherAssert.assertThat;
11 | import static org.hamcrest.Matchers.equalTo;
12 |
13 | /**
14 | * Test for {@link BeanPropertiesDemo}.
15 | */
16 | class BeanDemoTest {
17 |
18 | @Test
19 | void shouldOutputExpectedText() throws IOException {
20 | // given
21 | BeanPropertiesDemo beanDemo = new BeanPropertiesDemo();
22 |
23 | try {
24 | // Perform the actual test
25 | shouldOutputExpectedText(beanDemo);
26 | } finally {
27 | // Cleanup - delete the temporary file
28 | Path file = beanDemo.getConfigFile();
29 | if (file != null) {
30 | Files.delete(file);
31 | }
32 | }
33 | }
34 |
35 | private void shouldOutputExpectedText(BeanPropertiesDemo beanDemo) {
36 | // when
37 | String userInfo = beanDemo.generateUserInfo();
38 |
39 | // then
40 | String expectedText = "Saved locations of Richie: restaurant (47.5, 8.7), hospital (47.1, 8.8901)"
41 | + "\nNicknames of Bob: Bobby, Bobby boy"
42 | + "\nCountry 'Sweden' has neighbors: Norway, Finland, Denmark";
43 | assertThat(userInfo, equalTo(expectedText));
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/test/java/ch/jalu/configme/ConfigDemoTest.java:
--------------------------------------------------------------------------------
1 | package ch.jalu.configme;
2 |
3 | import ch.jalu.configme.demo.WelcomeWriter;
4 | import org.junit.jupiter.api.Test;
5 |
6 | import java.io.IOException;
7 | import java.nio.file.Files;
8 | import java.nio.file.Path;
9 |
10 | import static org.hamcrest.MatcherAssert.assertThat;
11 | import static org.hamcrest.Matchers.equalTo;
12 |
13 | /**
14 | * Test for {@link WelcomeWriter}.
15 | */
16 | class ConfigDemoTest {
17 |
18 | @Test
19 | void shouldGenerateExpectedHtml() throws IOException {
20 | // given
21 | WelcomeWriter writer = new WelcomeWriter();
22 |
23 | try {
24 | // Perform the actual test
25 | shouldGenerateExpectedHtml(writer);
26 | } finally {
27 | // Cleanup - delete the temporary file
28 | Path file = writer.getConfigFile();
29 | if (file != null) {
30 | Files.delete(file);
31 | }
32 | }
33 | }
34 |
35 | private void shouldGenerateExpectedHtml(WelcomeWriter writer) {
36 | // when
37 | String welcomeFile = writer.generateWelcomeFile();
38 |
39 | // then
40 | String expectedText = "