Added @Format annotation

This annotation can be used to apply a FieldNameFormatter
to a Configuration without having to instantiate a Properties
object. The FieldNameFormatter returned by the annotation
takes precedence over the value returned by the Properties object.

Added FieldNameFormatters.UPPER_UNDERSCORE
dev v2.2.0
Exlll 6 years ago
parent 58b81f3314
commit 38b1a1aca4

@ -1,5 +1,5 @@
name: ConfigLib
author: Exlll
version: 2.1.0
version: 2.2.0
main: de.exlll.configlib.ConfigLib

@ -1,5 +1,5 @@
name: ConfigLib
author: Exlll
version: 2.1.0
version: 2.2.0
main: de.exlll.configlib.ConfigLib

@ -1,5 +1,6 @@
package de.exlll.configlib;
import de.exlll.configlib.FieldMapper.MappingInfo;
import de.exlll.configlib.filter.FieldFilter;
import de.exlll.configlib.filter.FieldFilters;
import de.exlll.configlib.format.FieldNameFormatter;
@ -45,7 +46,9 @@ public abstract class Configuration<C extends Configuration<C>> {
public final void save() {
try {
preSave();
Map<String, Object> map = FieldMapper.instanceToMap(this, props);
MappingInfo mappingInfo = MappingInfo.from(this);
Map<String, Object> map = FieldMapper
.instanceToMap(this, mappingInfo);
getSource().saveConfiguration(getThis(), map);
} catch (IOException e) {
throw new ConfigurationStoreException(e);
@ -63,7 +66,8 @@ public abstract class Configuration<C extends Configuration<C>> {
public final void load() {
try {
Map<String, Object> map = getSource().loadConfiguration(getThis());
FieldMapper.instanceFromMap(this, map, props);
MappingInfo mappingInfo = MappingInfo.from(this);
FieldMapper.instanceFromMap(this, map, mappingInfo);
postLoad();
} catch (IOException e) {
throw new ConfigurationStoreException(e);
@ -100,6 +104,10 @@ public abstract class Configuration<C extends Configuration<C>> {
*/
protected void postLoad() {}
Properties getProperties() {
return props;
}
/**
* Instances of a {@code Properties} class are used to configure different
* aspects of a configuration.

@ -1,5 +1,6 @@
package de.exlll.configlib;
import de.exlll.configlib.FieldMapper.MappingInfo;
import de.exlll.configlib.annotation.ElementType;
import java.lang.reflect.Field;
@ -10,21 +11,21 @@ import java.lang.reflect.Field;
* <p>
* Implementations must have a no-args constructor.
*
* @param <F> the type of the field value
* @param <T> the type of the converted value
* @param <S> the source type
* @param <T> the target type
*/
public interface Converter<F, T> {
public interface Converter<S, T> {
/**
* Converts a field value to an object that can be stored by a
* {@code ConfigurationSource}.
* <p>
* If this method returns null, a {@code ConfigurationException} will be thrown.
* If this method returns null, a {@code ConfigurationException} is thrown.
*
* @param element field value that is converted
* @param info information about the current conversion step
* @return converted field value
*/
T convertTo(F element, ConversionInfo info);
T convertTo(S element, ConversionInfo info);
/**
* Executes some action before the field value is converted.
@ -43,7 +44,7 @@ public interface Converter<F, T> {
* @param info information about the current conversion step
* @return the element's original representation
*/
F convertFrom(T element, ConversionInfo info);
S convertFrom(T element, ConversionInfo info);
/**
* Executes some action before the converted field value is converted back
@ -58,6 +59,7 @@ public interface Converter<F, T> {
* configuration, configuration element, and the conversion step.
*/
final class ConversionInfo {
private final MappingInfo mappingInfo;
private final Field field;
private final Object instance;
private final Object value;
@ -70,8 +72,11 @@ public interface Converter<F, T> {
private final int nestingLevel;
private int currentNestingLevel;
private ConversionInfo(Field field, Object instance, Object mapValue,
Configuration.Properties props) {
private ConversionInfo(
Field field, Object instance, Object mapValue,
MappingInfo mappingInfo
) {
this.mappingInfo = mappingInfo;
this.field = field;
this.instance = instance;
this.value = Reflect.getValue(field, instance);
@ -79,7 +84,7 @@ public interface Converter<F, T> {
this.fieldType = field.getType();
this.valueType = value.getClass();
this.fieldName = field.getName();
this.props = props;
this.props = mappingInfo.getProperties();
this.elementType = elementType(field);
this.nestingLevel = nestingLevel(field);
}
@ -100,14 +105,17 @@ public interface Converter<F, T> {
return -1;
}
static ConversionInfo of(Field field, Object instance,
Configuration.Properties props) {
return new ConversionInfo(field, instance, null, props);
static ConversionInfo from(
Field field, Object instance, MappingInfo mappingInfo
) {
return new ConversionInfo(field, instance, null, mappingInfo);
}
static ConversionInfo of(Field field, Object instance, Object mapValue,
Configuration.Properties props) {
return new ConversionInfo(field, instance, mapValue, props);
static ConversionInfo from(
Field field, Object instance, Object mapValue,
MappingInfo mappingInfo
) {
return new ConversionInfo(field, instance, mapValue, mappingInfo);
}
/**
@ -215,5 +223,9 @@ public interface Converter<F, T> {
void incCurrentNestingLevel() {
currentNestingLevel++;
}
MappingInfo getMappingInfo() {
return this.mappingInfo;
}
}
}

@ -366,7 +366,7 @@ final class Converters {
return o -> {
Map<String, Object> map = toTypeMap(o, null);
Object inst = Reflect.newInstance(info.getElementType());
FieldMapper.instanceFromMap(inst, map, info.getProperties());
FieldMapper.instanceFromMap(inst, map, info.getMappingInfo());
return inst;
};
} else if ((element instanceof String) && currentLevelSameAsExpected) {
@ -524,7 +524,7 @@ final class Converters {
@Override
public Object convertTo(Object element, ConversionInfo info) {
return FieldMapper.instanceToMap(element, info.getProperties());
return FieldMapper.instanceToMap(element, info.getMappingInfo());
}
@Override
@ -538,7 +538,7 @@ final class Converters {
checkElementIsConvertibleToConfigurationElement(element, info);
Object newInstance = Reflect.newInstance(info.getValueType());
Map<String, Object> typeMap = toTypeMap(element, info.getFieldName());
FieldMapper.instanceFromMap(newInstance, typeMap, info.getProperties());
FieldMapper.instanceFromMap(newInstance, typeMap, info.getMappingInfo());
return newInstance;
}

@ -1,6 +1,7 @@
package de.exlll.configlib;
import de.exlll.configlib.Converter.ConversionInfo;
import de.exlll.configlib.annotation.Format;
import de.exlll.configlib.filter.FieldFilter;
import de.exlll.configlib.format.FieldNameFormatter;
@ -13,14 +14,13 @@ import static de.exlll.configlib.Validator.*;
enum FieldMapper {
;
static Map<String, Object> instanceToMap(
Object inst, Configuration.Properties props
) {
static Map<String, Object> instanceToMap(Object inst, MappingInfo mappingInfo) {
Map<String, Object> map = new LinkedHashMap<>();
Configuration.Properties props = mappingInfo.getProperties();
FieldFilter filter = props.getFilter();
for (Field field : filter.filterDeclaredFieldsOf(inst.getClass())) {
Object val = toConvertibleObject(field, inst, props);
FieldNameFormatter fnf = props.getFormatter();
Object val = toConvertibleObject(field, inst, mappingInfo);
FieldNameFormatter fnf = selectFormatter(mappingInfo);
String fn = fnf.fromFieldName(field.getName());
map.put(fn, val);
}
@ -28,10 +28,10 @@ enum FieldMapper {
}
private static Object toConvertibleObject(
Field field, Object instance, Configuration.Properties props
Field field, Object instance, MappingInfo mappingInfo
) {
checkDefaultValueNull(field, instance);
ConversionInfo info = ConversionInfo.of(field, instance, props);
ConversionInfo info = ConversionInfo.from(field, instance, mappingInfo);
checkFieldWithElementTypeIsContainer(info);
Object converted = Converters.convertTo(info);
checkConverterNotReturnsNull(converted, info);
@ -39,26 +39,27 @@ enum FieldMapper {
}
static void instanceFromMap(
Object inst, Map<String, Object> instMap,
Configuration.Properties props
Object inst, Map<String, Object> instMap, MappingInfo mappingInfo
) {
FieldFilter filter = props.getFilter();
FieldFilter filter = mappingInfo.getProperties().getFilter();
for (Field field : filter.filterDeclaredFieldsOf(inst.getClass())) {
FieldNameFormatter fnf = props.getFormatter();
FieldNameFormatter fnf = selectFormatter(mappingInfo);
String fn = fnf.fromFieldName(field.getName());
Object mapValue = instMap.get(fn);
if (mapValue != null) {
fromConvertedObject(field, inst, mapValue, props);
fromConvertedObject(field, inst, mapValue, mappingInfo);
}
}
}
private static void fromConvertedObject(
Field field, Object instance, Object mapValue,
Configuration.Properties props
MappingInfo mappingInfo
) {
checkDefaultValueNull(field, instance);
ConversionInfo info = ConversionInfo.of(field, instance, mapValue, props);
ConversionInfo info = ConversionInfo.from(
field, instance, mapValue, mappingInfo
);
checkFieldWithElementTypeIsContainer(info);
Object convert = Converters.convertFrom(info);
@ -77,4 +78,43 @@ enum FieldMapper {
Object val = Reflect.getValue(field, instance);
checkNotNull(val, field.getName());
}
static FieldNameFormatter selectFormatter(MappingInfo info) {
Configuration<?> configuration = info.getConfiguration();
Configuration.Properties props = info.getProperties();
if ((configuration != null) &&
Reflect.hasFormatter(configuration.getClass())) {
Format format = configuration.getClass()
.getAnnotation(Format.class);
return (format.formatterClass() != FieldNameFormatter.class)
? Reflect.newInstance(format.formatterClass())
: format.value();
}
return props.getFormatter();
}
static final class MappingInfo {
private final Configuration<?> configuration;
private final Configuration.Properties properties;
MappingInfo(
Configuration<?> configuration,
Configuration.Properties properties
) {
this.configuration = configuration;
this.properties = properties;
}
Configuration<?> getConfiguration() {
return configuration;
}
Configuration.Properties getProperties() {
return properties;
}
static MappingInfo from(Configuration<?> configuration) {
return new MappingInfo(configuration, configuration.getProperties());
}
}
}

@ -2,6 +2,7 @@ package de.exlll.configlib;
import de.exlll.configlib.annotation.ConfigurationElement;
import de.exlll.configlib.annotation.Convert;
import de.exlll.configlib.annotation.Format;
import de.exlll.configlib.annotation.NoConvert;
import java.lang.reflect.Constructor;
@ -37,22 +38,12 @@ enum Reflect {
return cls.isEnum();
}
static boolean hasConverter(Field field) {
return field.isAnnotationPresent(Convert.class);
}
static boolean hasNoConvert(Field field) {
return field.isAnnotationPresent(NoConvert.class);
}
static <T> T newInstance(Class<T> cls) {
try {
Constructor<T> constructor = cls.getDeclaredConstructor();
constructor.setAccessible(true);
return constructor.newInstance();
} catch (NoSuchMethodException e) {
/* This exception should not be thrown because we check
* the presence of a no-args constructor elsewhere. */
String msg = "Class " + cls.getSimpleName() + " doesn't have a " +
"no-args constructor.";
throw new ConfigurationException(msg, e);
@ -63,8 +54,6 @@ enum Reflect {
" not accessible.";
throw new ConfigurationException(msg, e);
} catch (InstantiationException e) {
/* This exception should not be thrown because
* we call this method only for concrete types. */
String msg = "Class " + cls.getSimpleName() + " not instantiable.";
throw new ConfigurationException(msg, e);
} catch (InvocationTargetException e) {
@ -98,6 +87,18 @@ enum Reflect {
}
}
static boolean hasConverter(Field field) {
return field.isAnnotationPresent(Convert.class);
}
static boolean hasNoConvert(Field field) {
return field.isAnnotationPresent(NoConvert.class);
}
static boolean hasFormatter(Class<?> cls) {
return cls.isAnnotationPresent(Format.class);
}
static boolean isConfigurationElement(Class<?> cls) {
return cls.isAnnotationPresent(ConfigurationElement.class);
}

@ -24,7 +24,17 @@ import java.lang.annotation.Target;
@Target(java.lang.annotation.ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ElementType {
/**
* Returns the type of elements a {@code Collection} or {@code Map} contains.
*
* @return type of elements.
*/
Class<?> value();
/**
* Returns the nesting level
*
* @return nesting level
*/
int nestingLevel() default 0;
}

@ -0,0 +1,38 @@
package de.exlll.configlib.annotation;
import de.exlll.configlib.format.FieldNameFormatter;
import de.exlll.configlib.format.FieldNameFormatters;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Indicates that a specific {@code FieldNameFormatter} is used. If a
* {@link #formatterClass()} is specified, the {@code FieldNameFormatters}
* returned by {@link #value()} is ignored. The {@code formatterClass} must
* be instantiable and must have a no-args constructor.
* <p>
* This annotation takes precedence over the value set in properties object.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Format {
/**
* Returns the {@code FieldNameFormatters} instance to be for formatting.
* The return value of this method is ignored if a {@code formatterClass()}
* is set.
*
* @return {@code FieldNameFormatters} instance
*/
FieldNameFormatters value() default FieldNameFormatters.IDENTITY;
/**
* Returns the class of a {@code FieldNameFormatter} implementation.
* The class must be instantiable and must have a no-args constructor.
*
* @return class of {@code FieldNameFormatter} implementation
*/
Class<? extends FieldNameFormatter> formatterClass() default FieldNameFormatter.class;
}

@ -31,5 +31,25 @@ public enum FieldNameFormatters implements FieldNameFormatter {
}
return builder.toString();
}
},
/**
* Represents a {@code FieldNameFormatter} that transforms <i>camelCase</i> to
* <i>UPPER_UNDERSCORE</i>.
* <p>
* For example, <i>myPrivateField</i> becomes <i>MY_PRIVATE_FIELD</i>.
*/
UPPER_UNDERSCORE {
@Override
public String fromFieldName(String fieldName) {
StringBuilder builder = new StringBuilder(fieldName.length());
for (char c : fieldName.toCharArray()) {
if (Character.isLowerCase(c)) {
builder.append(Character.toUpperCase(c));
} else if (Character.isUpperCase(c)) {
builder.append('_').append(c);
}
}
return builder.toString();
}
}
}

@ -1,5 +1,6 @@
package de.exlll.configlib;
import de.exlll.configlib.FieldMapper.MappingInfo;
import de.exlll.configlib.annotation.ConfigurationElement;
import java.util.Map;
@ -72,7 +73,8 @@ public class FieldMapperHelpers {
public static Map<String, Object> instanceToMap(
Object o, Configuration.Properties props
) {
return FieldMapper.instanceToMap(o, props);
MappingInfo mappingInfo = new MappingInfo(null, props);
return FieldMapper.instanceToMap(o, mappingInfo);
}
public static <T> T instanceFromMap(T o, Map<String, Object> map) {
@ -82,7 +84,8 @@ public class FieldMapperHelpers {
public static <T> T instanceFromMap(
T o, Map<String, Object> map, Configuration.Properties props
) {
FieldMapper.instanceFromMap(o, map, props);
MappingInfo mappingInfo = new MappingInfo(null, props);
FieldMapper.instanceFromMap(o, map, mappingInfo);
return o;
}
}

@ -1,11 +1,16 @@
package de.exlll.configlib;
import de.exlll.configlib.Converter.ConversionInfo;
import de.exlll.configlib.FieldMapper.MappingInfo;
import de.exlll.configlib.annotation.ElementType;
import de.exlll.configlib.annotation.Format;
import de.exlll.configlib.annotation.NoConvert;
import de.exlll.configlib.classes.TestClass;
import de.exlll.configlib.classes.TestSubClass;
import de.exlll.configlib.classes.TestSubClassConverter;
import de.exlll.configlib.configs.mem.InSharedMemoryConfiguration;
import de.exlll.configlib.configs.yaml.YamlConfiguration.YamlProperties;
import de.exlll.configlib.format.FieldNameFormatter;
import de.exlll.configlib.format.FieldNameFormatters;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -32,6 +37,10 @@ class FieldMapperTest {
private static final TestClass t = TestClass.TEST_VALUES;
private static final Configuration.Properties DEFAULT =
Configuration.Properties.builder().build();
private static final YamlProperties WITH_UPPER_FORMATTER = YamlProperties
.builder()
.setFormatter(FieldNameFormatters.UPPER_UNDERSCORE)
.build();
private static final Map<String, Object> map = instanceToMap(
TestClass.TEST_VALUES, DEFAULT
);
@ -41,7 +50,8 @@ class FieldMapperTest {
@BeforeEach
void setUp() {
tmp = new TestClass();
FieldMapper.instanceFromMap(tmp, map, DEFAULT);
MappingInfo mappingInfo = MappingInfo.from(tmp);
FieldMapper.instanceFromMap(tmp, map, mappingInfo);
}
@Test
@ -377,7 +387,8 @@ class FieldMapperTest {
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
return ConversionInfo.of(field, o, null, null);
MappingInfo mappingInfo = new MappingInfo(null, WITH_UPPER_FORMATTER);
return ConversionInfo.from(field, o, mappingInfo);
}
private static ConversionInfo newInfo(String fieldName) {
@ -502,4 +513,80 @@ class FieldMapperTest {
assertThat(a.d, is(4));
assertThat(a.e, is(5));
}
@Test
void selectFieldNameFormatterReturnsPropertiesIfNoFormatAnnotation() {
class A extends InSharedMemoryConfiguration {
protected A() { super(WITH_UPPER_FORMATTER); }
}
A a = new A();
FieldNameFormatter fnf = FieldMapper.selectFormatter(MappingInfo.from(a));
assertThat(fnf, sameInstance(FieldNameFormatters.UPPER_UNDERSCORE));
}
@Test
void selectFieldNameFormatterReturnsIfFormatAnnotation() {
@Format(FieldNameFormatters.LOWER_UNDERSCORE)
class A extends InSharedMemoryConfiguration {
protected A() { super(WITH_UPPER_FORMATTER); }
}
@Format(formatterClass = MyFormatter.class)
class B extends InSharedMemoryConfiguration {
protected B() { super(WITH_UPPER_FORMATTER); }
}
A a = new A();
FieldNameFormatter fnf = FieldMapper.selectFormatter(MappingInfo.from(a));
assertThat(fnf, sameInstance(FieldNameFormatters.LOWER_UNDERSCORE));
B b = new B();
fnf = FieldMapper.selectFormatter(MappingInfo.from(b));
assertThat(fnf, instanceOf(MyFormatter.class));
}
@Test
void formatterClassTakesPrecedence() {
@Format(
value = FieldNameFormatters.LOWER_UNDERSCORE,
formatterClass = MyFormatter.class
)
class A extends InSharedMemoryConfiguration {
protected A() { super(WITH_UPPER_FORMATTER); }
}
A a = new A();
FieldNameFormatter fnf = FieldMapper.selectFormatter(MappingInfo.from(a));
assertThat(fnf, instanceOf(MyFormatter.class));
}
private static final class MyFormatter implements FieldNameFormatter {
@Override
public String fromFieldName(String fieldName) {
return fieldName;
}
}
@Test
void fieldMapperUsesSelectFieldNameFormatter() {
@Format(FieldNameFormatters.LOWER_UNDERSCORE)
class A extends InSharedMemoryConfiguration {
private int abc = 1;
private int eFg = 2;
private int hIJ = 3;
protected A() { super(WITH_UPPER_FORMATTER); }
}
A a = new A();
MappingInfo mappingInfo = MappingInfo.from(a);
Map<String, Object> map = FieldMapper.instanceToMap(a, mappingInfo);
assertThat(map.get("abc"), is(1));
assertThat(map.get("e_fg"), is(2));
assertThat(map.get("h_i_j"), is(3));
map = mapOf("abc", 4, "e_fg", 5, "h_i_j", 6);
FieldMapper.instanceFromMap(a, map, mappingInfo);
assertThat(a.abc, is(4));
assertThat(a.eFg, is(5));
assertThat(a.hIJ, is(6));
}
}

@ -21,4 +21,11 @@ class FieldNameFormattersTest {
assertThat(formatter.fromFieldName("fieldNameFormat"), is("field_name_format"));
}
@Test
void upperUnderscoreConvertsFromAndToCamelCase() {
FieldNameFormatter formatter = FieldNameFormatters.UPPER_UNDERSCORE;
assertThat(formatter.fromFieldName("fieldNameFormat"), is("FIELD_NAME_FORMAT"));
}
}

@ -293,6 +293,16 @@ YamlProperties properties = YamlProperties.builder()
.build();
```
Alternatively, you can annotate your `Configuration` class with the `@Format` annotation. The `FieldNameFormatter`
returned by this annotation takes precedence over the `FieldNameFormatter` returned by a `Properties` object.
```java
@Format(FieldNameFormatters.UPPER_UNDERSCORE)
class MyConfiguration extends YamlConfiguration {
// ...
}
```
Note: You should neither remove nor replace a formatter with one that has a different formatting style because this
could break existing configurations.
@ -465,14 +475,14 @@ public final class DatabasePlugin extends JavaPlugin {
<dependency>
<groupId>de.exlll</groupId>
<artifactId>configlib-bukkit</artifactId>
<version>2.1.0</version>
<version>2.2.0</version>
</dependency>
<!-- for Bungee plugins -->
<dependency>
<groupId>de.exlll</groupId>
<artifactId>configlib-bungee</artifactId>
<version>2.1.0</version>
<version>2.2.0</version>
</dependency>
```
#### Gradle
@ -484,9 +494,9 @@ repositories {
}
dependencies {
// for Bukkit plugins
compile group: 'de.exlll', name: 'configlib-bukkit', version: '2.1.0'
compile group: 'de.exlll', name: 'configlib-bukkit', version: '2.2.0'
// for Bungee plugins
compile group: 'de.exlll', name: 'configlib-bungee', version: '2.1.0'
compile group: 'de.exlll', name: 'configlib-bungee', version: '2.2.0'
}
```

@ -1,6 +1,6 @@
allprojects {
group 'de.exlll'
version '2.1.0'
version '2.2.0'
}
subprojects {
apply plugin: 'java'

Loading…
Cancel
Save